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

         

Сервер


Сервер — любой отдельно взятый компьютер в Интернете, который позволяет другим машинам, грубо говоря, использовать себя в качестве "посред­ника" при передаче данных. Также все серверы участвуют в вышеописанной "лавине" поиска компьютера по ее IP-адресу, на многих хранится какая-то информация, доступная или нет извне. Сервер — это именно машина ("железо"), а не логическая часть Сети, он может иметь несколько различных IP-адресов (не говоря уже о доменных именах), так что вполне может выглядеть из Интернета как несколько независимых систем.

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

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


Формат: Server: Apache/1.3.9 (Unix) PHP/3.0.12

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



SERVER_PORT


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



Сессии и Cookies


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

К сожалению, пользователи отключают Cookies гораздо чаще, чем это может показаться на первый взгляд. Например, всего год назад Всероссийский Клуб Вебмастеров проводил опрос, в результате которого выяснилось, что количество пользователей Интернета, отключивших у себя по каким-то соображениям поддержку Cookies, достигает 20—30%. Что это за соображения? Многие думают, что Cookies потенциально являются "дырой" в безопасности их компьютера. Это совершенно не соответствует действительности, потому что браузеры всегда имеют ограничения на количество и суммарный объем Cookies, которые могут быть в них установлены. Другие же просто не хотят, чтобы неизвестно кто писал что угодно на их жесткий диск. Правда, это не мешает таким "пе­ре­страховщикам" открывать пришедший по почте исполняемый[E129] 

файл — такой же, как из письма типа [DK130] "Love letter"…

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



Set-cookie


Формат: Set-cookie: параметры_cookie

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



Сетевой демон


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

Сам термин "сетевой демон" возник на базе устоявшейся терминологии Unix.

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

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

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



Сетевые функции


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



Шаблон страницы


Пусть нам нужно завести новый раздел сайта— гостевую книгу. Выделим для нее отдельный каталог на сервере и создадим в нем файл примерно следующего содержания (листинг 30.1). Назовем его шаблоном страницы.

Листинг 30.1. Шаблон: gbook.htm

<html><head><title>Гостевая книга</title></head>

<body>

<h2>Добавьте свое сообщение:</h2>

<form action=gbook.php method=post>

Ваше имя: <input type=text name="New[name]"><br>

Комментарий:<br>

<textarea name="New[text]" wrap=virtual cols=60 rows=5></textarea><br>

<input type=submit name="doAdd" value="Добавить!">

</form>

<h2>Гостевая книга:</h2>

<?foreach($Book as $id=>$Entry) {?>

  Имя человека: <?=$Entry['name']?><br>

  Его комментарий:<br> <?=$Entry['text']?><hr>

<?}?>

</body></html>

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

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

<foreach src=Book>

  Имя человека: $name<br>

  Его комментарий:<br>$text<hr>

</foreach>

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

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

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


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

Листинг 30.3. Шаблон: gbook.html

<?include "gbook.php"?>

<html><head><title>Гостевая книга</title></head>

<body>

<h2>Добавьте свое сообщение:</h2>

<form action=gbook.html method=post>

Ваше имя: <input type=text name="New[name]"><br>

. . .

Я не буду приводить текст страницы целиком, т. к. после определения формы он идентичен листингу 30.1. Итак, мы помещаем инструкцию include самой первой строчкой шаблона, и на это есть своя причина. Дело в том, что при различных, скажем так, "аварийных" событиях генератор данных может перенаправить браузер на другой адрес, не вернув управление в шаблон. Конечно, если бы include размещалась где-нибудь в середине шаблона, мы не смогли бы этого сделать, поскольку часть страницы могла быть уже отослана пользователю.

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

Заметьте, что шаблон имеет расширение HTML и выглядит для пользователя как обычная HTML-страница. Пользователь может и не подозревать, что в действительности сценарий написан на PHP. Но, чтобы описанный механизм заработал, нам необходимо связать расширение HTML с обработчиком PHP. Мы уже делали это в главе 29. Вот какую строчку нужно добавить в файл .htaccess, расположенный в каталоге (или "надкаталоге") сценария:

AddHandler application/x-httpd-php .html

Мы должны использовать директиву AddHandler, а не AddType, на случай, если для расширения HTML был ранее установлен другой обработчик. Им может быть, например, SSI (Server-Side Includes — Включения на стороне сервера) или даже PHP версии 3. В этом случае директива AddType "не срабатывает".

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

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

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



Шаблонизатор


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

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

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

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

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

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

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



Символические ссылки


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

$a=10;

$b=20;

$c=30;

$p="a";   // èëè $p="b" èëè $p="c" (ïðèñâàèâàåì $p èìÿ äðóãîé ïåðåìåííîé)

echo $$p; // âûâîäèò ïåðåìåííóþ, íà êîòîðóþ ññûëàåòñÿ $p, ò. å. $a

$$p=100;  // ïðèñâàèâàåò $a çíà÷åíèå 100

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

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

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


Для начала (для тех, кто не знает) — что это такое? В системе Unix (да и в других ОС в общем-то тоже) довольно часто возникает необходимость иметь для одного и того же файла или каталога разные имена. При этом логично одно из имен назвать основным, а все другие — его псевдонимами[E74] . В терминологии Unix такие псевдонимы называются символическими ссылками.

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

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

string readlink(string  $linkname)

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

bool symlink(string $target, string $link)

Эта функция создает символическую ссылку с именем $link на объект (файл или каталог), заданную в $target. В случае "провала"

функция возвращает false.

array lstat(string $filename)

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

int linkinfo(string $linkname)

Функция возвращает значение поля "устройство" из результата, выдаваемого функцией lstat(), которую мы рассматривали выше. Ее обычно задействуют, если хотят определить, существует ли еще объект, на который указывает символическая ссылка в $linkname. Я предпочитаю пользоваться для этого вызовом stat(), т. к., по-моему, ее название несколько более "читабельно".

Нужно добавить, что можно совершенно спокойно удалять символические ссылки, не опасаясь за содержимое основного файла. Это делается обычным способом — например, вызовом unlink() или rmdir().



Скрытое текстовое поле (hidden)


<input type=hidden

  name=èìÿ

  value=çíà÷åíèå

>

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

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

<form action=/cgi/sendmail.cgi method=post>

<input type=hidden name=email value="admin.microsoft.com.">

<h2>Ïîøëèòå ñîîáùåíèå àäìèíèñòðàòîðó:</h2>

<input type=text name="text">

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

</form>

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

<form action=/cgi/sendmail.cgi method=post>

<h2>Ïîøëèòå ñîîáùåíèå äðóãó:</h2>

Åãî E-mail: <input type=text name=email><br>

Òåêñò: <input type=text name="text"><br>

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

</form>

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


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



В некоторых случаях именованные кнопки submit не помогают, и приходится пользоваться скрытым полем для индикации запуска сценария из формы. Происходит это в случае, если форма очень проста и состоит, например, всего из двух элементов — поля ввода текста и кнопки submit (пусть даже и именованной). Практически все браузеры в такой ситуации позволяют пользователю просто нажать <Enter> для отправки формы, а не возиться с нажатием на submit-кнопку. При этом разумеется, данные кнопки не посылаются на сервер. Вот тогда-то нас и выручит hidden-поле, например, с именем submit: если его значение установлено, то сценарий понимает, что пользователь ввел какие-то данные, в противном случае сценарий был запущен впервые путем набора его URL или перехода по гиперссылке.


Слияние массивов


Еще одна фундаментальная операция— слияние массивов, т. е. создание массива, содержащего как элементы одного, так и другого массива. Реализуется это при помощи оператора +. Например:

$a=array("a"=>"aa", "b"=>"bb");

$b=array("c"=>"cc", "d"=>"dd");

$c=$a+$b;

В результате в $c окажется ассоциативный массив, содержащий все 4 элемента, а именно: array("a"=>"aa", "b"=>"bb", "c"=>"cc", "d"=>"dd"), причем именно в указанном порядке. Если бы мы написали $c=$b+$a, результат бы был немного другой, а именно: array("c"=>"cc", "d"=>"dd", "a"=>"aa", "b"=>"bb"), т. е. элементы расположены в другом порядке. Видите, как проявляется направленность массивов? Она заставляет оператор +

стать некоммутативным, т. е. $a+$b

не равно $b+$a, если $a

и $b — массивы.

Будьте особенно внимательны при слиянии таким образом списков. Рассмотрим следующие операторы:

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

$b=array(100,200);

$c=$a+$b;

Возможно, вы рассчитываете, что в $c будет array(10,20,30,100,200)? Это неверно: там окажется array(10,20,30). Вот почему так происходит. При конкатенации массивов с некоторыми одинаковыми элементами (то есть, элементами с одинаковыми ключами) в результирующем массиве останется только один элемент с таким же ключом — тот, который был в первом массиве, и на том же самом месте.

Последний факт может слегка озадачить. Казалось бы, элементы массива $b по логике должны заменить элементы из $a. Однако все происходит наоборот. Окончательно выбивает из колеи следующий пример:

$a=array('a'=>10, 'b'=>20);

$b=array('c'=>30, 'b'=>'new?');

$a+=$b;

Мы-то ожидали, что оператор +=

обновит элементы $a при помощи элементов $b. À напрасно. В результате этих операций значение $a не изменится! Если вы не верите своим глазам, можете проверить.


Так как же нам все-таки обновить элементы в массиве $a? Получается, только прямым способом — с помощью цикла:

foreach ($b as $k=>$v) $a[$k]=$v;

Что поделать, так уж распорядились разработчики PHP.

Еще несколько слов насчет операции слияния массивов. Цепочка

$z=$a+$b+$c+...и т. д.;

эквивалентна

$z=$a; $z+=$b; $z+=$c; ...è ò. ä.

Как нетрудно догадаться, оператор += для массивов делает примерно то же, что и оператор += для чисел, а именно — добавляет в свой левый операнд элементы, перечисленные в правом операнде-массиве, если они еще не содержатся

в массиве слева.

Итак, в массиве никогда не может быть двух элементов с одинаковыми ключами, потому что все операции, применимые к массивам, всегда контролируют, чтобы этого не произошло. Впрочем, на мой взгляд, данное свойство вовсе не достоинство, а недостаток — вполне можно было бы позволить оператору + оставлять одинаковые ключи, а всем остальным — запретить это делать. Что ж, разработчики PHP "пошли другим путем"[E48] ...



Так как списки являются тоже ассоциативными массивами, оператор + будет работать с ними неправильно! Например, в результате слияния списков array(10,20) и array(100,200,300) получится список array(10,20,300) — всего из трех элементов! Согласитесь, ведь это совсем не то, что вы ожидали увидеть, не правда ли?..


ÓСловно определяемые функции


Предположим, у нас в программе где-то устанавливается переменная $OS_TYPE в значение win, если сценарий запущен под Windows 9x, и в unix, если под Unix. Как известно, в отличие от Unix, в Windows нет такого понятия, как владелец файла, а значит, стандартная функция chown() (которая как раз и назначает владельца для указанного файла) там просто не имеет смысла. В некоторых версиях PHP для Windows ее может в этой связи вообще не быть. Однако, чтобы улучшить переносимость сценариев с одной платформы на другую (без изменения их кода!) можно написать следующую простую "обертку"

для функции chown() (листинг11.14):

Листинг 11.14. Условно определяемые функции

if($OS_TYPE=="win")

{ // Функция-заглушка

  function MyChOwn($fname,$attr)

  { // íè÷åãî íå äåëàåò

    return 1;

  }

}

else

{ // Передаем вызов настоящей chown()

  function MyChOwn($fname,$attr)

  { return chown($fname,$attr);

  }

}

Это — один из примеров условно определяемых функций. Если мы работаем под Windows, функция MyChOwn() ничего не делает и возвращает 1 как индикатор успеха, в то время как для Unix она просто вызывает оригинальную chown(). Важно то, что проверка, какую функцию использовать, производится только один раз (в момент прохождения точки определения функции), т. е. здесь нет ни малейшей потери производительности. Теперь в сценарии мы должны всюду отказаться от chown() и использовать MyChOwn() (можно даже провести поиск/замену этого имени в редакторе) — это обеспечит переносимость.

Если вам совсем не нравится идея поиска/замены (а мне она не нравится категорически), то существует гораздо более элегантный способ, но только в том случае, если chown() еще не была нигде определена — в том числе и среди стандартных функций:

if(!function_exists("chown"))

{ function chown($fname,$mode)

  { // íå äåëàåì íè÷åãî


    return 1;

  }

}

Этот способ работает независимо от того, появится ли вдруг в будущих версиях PHP для Windows "заглушка"

для функции chown(), или же нет. (Нужно сказать для справедливости, что в PHP версии 4 такая заглушка  уже существует.)

Знатоки Си могут заметить в приеме условно определяемых функций разительное сходство с директивами условной компиляции этого языка: #ifndef, #else и #endif. Действительно, аналогия почти полная, за исключением того факта, что в Си эти директивы обрабатываются во время компиляции, а в PHP — во время выполнения. Что ж, на то он и интерпретатор, чтобы позволять себе интерпретацию.



То, что возможно создавать условно определяемые функции, сильно подрывает веру в PHP как в истинный транслятор. Как вообще можно устроить транслятор так, чтобы он правильно обрабатывал подобные вещи? Я не знаю. Надеюсь, что разработчики PHP нашли-таки способ, и условно определяемые функции транслируются вместе со всей программой, а не на этапе исполнения, как это было в PHP версии 3. Однако полной уверенности в этом нет, а документация по этому поводу молчит (пока).


Сложность перестановки блоков


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

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



Сложность смены шаблона у части страниц


Еще один недостаток описанной схемы следует из предыдущего. Каждая страница должна "знать", где расположены файлы header.htm и footer.htm. Пусть у нас на сайте есть каталог, в котором "лежат" сотни файлов. Во время разработки оказалось, что шаблон для всех файлов в этом каталоге должен отличаться от шаблона всех остальных страниц (которых также немало). Значит, требуется создать еще одну пару header- и footer-файлов, назвав их, например, header1.htm и footer1.htm. Это в общем-то не представляет особой проблемы, сложность в другом: придется заменять ссылки во всех файлах каталога. Можно, конечно, сделать это посредством глобальных поиска и замены при помощи какого-нибудь текстового редактора (например, HomeSite фирмы Allaire), но, согласитесь, это решение выглядит как явно "лобовое". Кроме того, если мы имеем доступ к сайту только с использованием FTP, нам придется "скачивать" все страницы, редактировать их, а затем опять копировать на сервер. Естественно, для крупных информационных сайтов такие "накладные расходы" просто неприемлемы.

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

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



Сложные имена полей


Как вы, наверное, помните, элементы формы могут иметь имена, выглядящие, как элементы массива: A[10], B[1][text] и т.д. До недавнего времени (в третьей версии PHP) это касалось только "обычных" полей, но не полей закачки файлов. К счастью, в PHP версии 4 все изменилось в лучшую сторону.

Давайте применим указанные возможности в следующем примере формы и определим, какие переменные создаст PHP при ее отправке на сервер.

<form action="script.php" method=POST enctype=multipart/form-data>

<h3>Выберите тип файлов в вашей системе:</h3>

Текстовый файл: <input type=file name="File[text]"><br>

Бинарный файл: <input type=file name="File[bin]"><br>

Картинка: <input type=file name="File[pic]"><br>

<input type=submit name=Go value="Отправить файлы">

</form>

После того как программа script.php примет данные из формы, PHP создаст для нее следующие переменные:

r    ассоциативный массив $File, ключи которого — text, bin и pic, а соответствующие значения — имена временных файлов на сервере, созданных PHP при загрузке;

r    массив $File_name

все с теми же ключами и значениями — именами файлов в системе пользователя;

r    массив $File_type

с теми же ключами и значениями — типами соответствующих файлов;

r    массив $File_size

со значениями — размерами этих файлов.

Мы видим, информация об индексах в именах полей формы попала в ключи соответствующих массивов и сохранилась в них. Вы можете убедиться в том, что переменные действительно инициализированы, воспользовавшись вызовом функции Dump($GLOBALS), код которой приведен в конце главы 11, и в полезности которой вы теперь можете убедиться на примере.

Еще раз напоминаю, что PHP версии 3 неправильно работает с подобными именами полей. Учитывайте это, если собираетесь использовать старый интерпретатор.



Соединение с базой данных


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

int mysql_connect([string $hostname] [,string $username]

                  [,string $password])

Функция mysql_connect() устанавливает сетевое соединение с базой данных MySQL, расположенной на хосте $hostname (по умолчанию это localhost, т. е. текущий компьютер), и возвращает идентификатор открытого соединения. Вся дальнейшая работа ведется именно с этим идентификатором. При регистрации указывается имя пользователя $username и пароль $password (по умолчанию имя пользователя, от которого запущен текущий процесс, и пустой пароль). Строка $hostname также может включать в себя номер порта в формате: имя_хоста:порт (если сервер MySQL настроен не на стандартный, а на какой-то другой порт, что делать, вообще говоря, не рекомендуется).

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

int mysql_select_db(string $dbname [,int $link_identifier])

До того как послать первый запрос серверу MySQL, необходимо указать, с какой базой данных мы собираемся работать. Для этого и предназначена описываемая функция. Она уведомляет PHP, что в дальнейших операциях с соединением $link_identifier

(или с последним открытым соединением, если указанный параметр не задан) будет использоваться база данных $dbname.



Сохранение изображения


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

int imagePng(int $im [,string $filename]) èëè

int imageJpeg(int $im [,string $filename]) èëè

int imageGif(int $im [,string $filename])

Эти функции сохраняют изображение, заданное своим идентификатором и находящееся в памяти, на диск, или же выводят его в браузер. Разумеется, вначале изображение должно быть загружено или создано при помощи функции imageCreate(), ò. å. мы должны знать его идентификатор $im.

Если аргумент $filename опущен, то сжатые данные в соответствующем формате выводятся прямо в стандартный выходной поток, т. е. в браузер. Нужный заголовок Content-type при этом не выводится, ввиду чего нужно выводить его вручную при помощи Header(), как это было показано в примере из листинга 23.1.

Некоторые браузеры не требуют вывода правильного Content-type, а определяют, что перед ними рисунок, по нескольким первым байтам присланных данных. Ни в коем случае не полагайтесь на это! Дело в том, что все еще существуют браузеры, которые этого делать не умеют. Кроме того, такая техника идет вразрез со стандартами HTTP.

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

Header("Content-type: image/png") äëÿ PNG

Header("Content-type: image/jpeg") äëÿ JPEG

Header("Content-type: image/gif") äëÿ GIF

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

К рассмотренным только что функциям можно сделать точно такие же замечания, как и к семейству imageCreateFromXXX(), т. е., некоторые из них могут отсутствовать — скорее всего, последняя. Однако случаются и забавные казусы. Я видел версию PHP, в которой не поддерживалась вообще ни одна из этих функций, ровно как и функции imageCreateFromXXX(). В то же время imageCreate() работала (во всяком случае, так казалось). Возникает интересный вопрос: мы можем создавать изображения, рисовать в них линии, круги, выводить текст, но не в состоянии ни сохранить их где-нибудь, ни даже загрузить уже готовую картинку с диска. Зачем тогда вообще были нужны все остальные функции?..



Сопоставление


bool ereg(string $expr, string $str [,list &$Matches])

Функция пытается сопоставить выражение $expr строке $str и в случае удачи возвращает true, иначе — false. Если совпадение было найдено, то в список $Matches (конечно, если он задан) записываются отдельные участки совпадения (как выделять эти участки на языке RegEx, мы рассмотрим немного позже). Пока скажу только, что в $Matches[0] всегда будет возвращаться подстрока совпадения целиком.



Сопоставление с заменой


Если нам нужно не определить, удовлетворяет ли строка выражению, а заменить в ней все подстроки, ему удовлетворяющие, на что-то еще, следует воспользоваться следующей функцией:

string ereg_replace(string $expr, strint $str, string $strToChange)

Эта функция занимается тем, что ищет в строке $str все подстроки, соответствующие[E87] выражению $expr, и заменяет их на $strToChange. В строке $strToChange могут содержаться некоторые управляющие символы, позволяющие обеспечить дополнительные возможности при замене. Их мы рассмотрим позже, а сейчас скажу только, что сочетание \0 (в PHP эта строка будет записываться как "\\0") будет заменено на найденное совпадение целиком.



Сортировка массива по значениям (asort()/arsort())


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

наверх, а некоторые — наоборот, "опускаются". Например:

$A=array("a"=>"Zero","b"=>"Weapon","c"=>"Alpha","d"=>"Processor");

asort($A);

foreach($A as $k=>$v) echo "$k=>$v  ";

// âûâîäèò "c=>Alpha  d=>Processor  b=>Weapon  a=>Zero"

// êàê âèäèì, ïîìåíÿëñÿ òîëüêî ïîðÿäîê ïàð êëþ÷=>çíà÷åíèå

Функция arsort() выполняет то же самое, за одним исключением: она упорядочивает массив не по возрастанию, а по убыванию.



Сортировка по ключам (ksort()/krsort())


Функция ksort() практически идентична функции asort(), с тем различием, что сортировка осуществляется не по значениями, а по ключам (в порядке возрастания). Например:

$A=array("d"=>"Zero", "c"=>"Weapon", "b"=>"Alpha", "a"=>"Processor");

ksort($A);

for(Reset($A); list($k,$v)=each($A);) echo "$k=>$v  ";

// âûâîäèò "a=>Processor  b=>Alpha  c=>Weapon  d=>Zero"

Функция для сортировки по ключам в обратном порядке называется krsort() и применяется точно в таком же контексте, что и ksort().



Сортировка по ключам при помощи функции uksort()


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

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

Листинг 13.1. Сортировка с помощью пользовательской функции

// Ýòà ôóíêöèÿ äîëæíà ñðàâíèâàòü çíà÷åíèÿ $f1 è $f2 è âîçâðàùàòü:

// -1, åñëè $f1<$f2,

//  0, åñëè $f1==$f2

//  1, åñëè $f1>$f2

// Ïîä < è > ïîíèìàåòñÿ ñëåäîâàíèå ýòèõ èìåí â âûâîäèìîì ñïèñêå

function FCmp($f1,$f2)

{  // Каталог âñåãäà ïðåäøåñòâóåò ôàéëó

   if(is_dir($f1) && !is_dir($f2)) return -1;

   // Ôàéë âñåãäà èäåò ïîñëå каталога

   if(!is_dir($f1) && is_dir($f2)) return 1;

   // Èíà÷å ñðàâíèâàåì ëåêñèêîãðàôè÷åñêè


   if($f1<$f2) return -1; elseif($f1>$f2) return 1; else return 0;

}

// Ïóñòü $Files ñîäåðæèò ìàññèâ ñ êëþ÷àìè — èìåíàìè ôàéëîâ

// â òåêóùåм каталоге. Îòñîðòèðóåì åãî.

uksort($Files,"FCmp");  // ïåðåäàåì ôóíêöèþ ñîðòèðîâêè "ïî ññûëêå"

Конечно, связи между ключами и значениями функцией uksort() сохраняются, т. е., опять же, некоторые пары просто "всплывают"

наверх, а другие — "оседают".


Сортировка по значениям при помощи функции uasort()


Функция uasort() очень похожа на uksort(), с той разницей, что сменной (пользовательской) функции сортировки "подсовываются"

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



Сортировка списка при помощи функции usort()


Эта функция как бы является "гибридом"

функций uasort() и sort(). От sort() она отличается тем, что критерий сравнения обеспечивается пользовательской функцией. А от uasort() — тем, что она не сохраняет связей между ключами и значениями, а потому пригодна разве что для сортировки списков. Вот тривиальный пример:

function FCmp($a,$b) { return strcmp($a,$b); }

$A=array("One","Two","Three","Four");

usort($A);

for($i=0; $i<count($A); $i++) echo "$i:$A[$i]  ";

// âûâîäèò "0:Four  1:One  2:Three  3:Two"

Использованная нами функция strcmp(), как и ее пращур в Си, возвращает -1, если $a<$b, 0, если они равны, и 1, если $a>$b. В принципе, приведенный здесь пример полностью эквивалентен простому вызову sort().



Сортировка списка sort()/rsort()


Эти две функции предназначены в первую очередь для сортировки списков (напоминаю, что под списками я понимаю массивы, ключи которых начинаются с 0 и не имеют пропусков). Функция sort() сортирует список (разумеется, по значениям) в порядке возрастания, а rsort()— в порядке убывания. Например:

$A=array("One", "Two", "Three", "Four");

sort($A);

for($i=0; $i<count($A); $i++) echo "$i:$A[$i]  ";

// âûâîäèò "0:Four  1:Two  2:Three  3:One"

Любой ассоциативный массив воспринимается этими функциями как список. То есть после упорядочивания последовательность ключей превращается в 0,1,2,..., а значения нужным образом перераспределяются. Как видим, связи между парами ключ=>значение не сохраняются, более того — ключи просто пропадают, поэтому сортировать что-либо, отличное от списка, вряд ли целесообразно.



Создание массива "на лету". Автомассивы


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

$NamesList[]="Dmitry";

$NamesList[]="Helen";

$NamesList[]="Sergey";

В этом случае PHP сам начнет (конечно, если переменная $NamesList еще не существует) нумерацию с нуля и каждый раз будет прибавлять к счетчику по единичке, создавая список. Согласитесь, довольно удобно. Разумеется, можно использовать [] и не только в таком простом контексте, очень часто они применяются для более общего действия — добавления элемента в конец массива, например:

Unset($FNames); // на всякий случай стираем массив

while($f=î÷åðåäíîå_èìÿ_ôàéëà_â_òåêóùåм каталоге)

  if(ðàñøèðåíèå_$f_åñòü_txt) $FNames[]=$f;

// òåïåðü $FNames ñîäåðæèò ñïèñîê ôàéëîâ ñ ðàñøèðåíèåì txt

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

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

$Names["Koteroff"] = "Dmitry";

$Names["Ivanov"]   = "Ivan";

$Names["Petrov"]   = "Peter";

Далее, мы можем распечатать имя любого абонента командой:

echo $Names["Ivanov"];

$f="Koteroff";

echo $Names[$f];

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



Создание нового цвета


int imageColorAllocate(int $im, int $red, int $green, int $blue)

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

— станет ясно после рассмотрения функции imageColorClosest().



Создание списка – диапазона чисел


list range(int $low, int $high)

Эта функция очень простая. Она создает список, заполненный целыми числами от $low до $high

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

<table>

<?foreach(range(1,100) as $i) {?>

  <tr>

  <td><?=$i?></td>

  <td>Это строка номер <?=$i?></td>

  </tr>

<?}?>

</table>

С точки зрения дизайнеров (не знакомых с PHP, но которым придется модифицировать внешний вид вашего сценария) представленный подход выглядит явно лучше, чем следующий фрагмент:

<table>

<?for($i=1; $i<=100; $i++) {?>

  <tr>

  <td><?=$i?></td>

  <td>Это строка номер <?=$i?></td>

  </tr>

<?}?>

</table>



Создание таблицы


create table ÈìÿÒàáëèöû(ÈìÿÏîëÿ òèï, ÈìÿÏîëÿ òèï, ...)

Этой командой в базе данных создается новая таблица с колонками (по­лями), определяемыми своими именами (ИмяПоля) и указанными типами.



Специализированные функции


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

int fileatime(string $filename)

Возвращает время последнего доступа (access) к файлу (например, на чтение). Время выражается в количестве секунд, прошедших с 1 января 1970 го-да. Если файл не обнаружен, возвращает false.

int filemtime(string $filename)

Возвращает время последнего изменения файла или false в случае отсутствия файла.

int filectime(string $filename)

Возвращает время создания файла.

int filesize(string $filename)

Возвращает размер файла в байтах или false, если файла не существует.

int touch(string $filename [, int $timestamp])

Устанавливает время модификации указанного файла $filename равным $timestamp (в секундах, прошедших с 1 января 1970[E69]  года). Если второй параметр не указан, то подразумевается текущее время. В случае ошибки возвращается false.

Если файла с указанным именем не существует, он создается пустым.



Списки и ассоциативные массивы: путаница?..


Следует сказать несколько слов насчет ассоциативных массивов языка PHP. Во-первых, на самом деле все "остальные"

массивы также являются ассоциативными (в частности, списки — тоже). Во-вторых, ассоциативные массивы в PHP являются направленными, т. е. в них существует определенный

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

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

Операция [] всегда добавляет элемент в конец массива, присваивая ему при этом такой числовой индекс, который бы не конфликтовал с уже имеющимися в массиве (точнее, выбирается номер, превосходящий все имеющиеся цифровые ключи в массиве). Вообще говоря, любая операция $Array[ключ]=значение всегда добавляет элемент в конец массива, конечно, за исключением тех случаев, когда ключ уже присутствует в массиве. Если вы захотите изменить порядок следования элементов в ассоциативном массиве, не изменяя в то же время их ключей, это можно сделать одним из двух способов: воспользоваться функциями сортировки, либо же создать новый пустой массив и заполнить его в нужном порядке, пройдясь по элементам исходного массива.



Списки и строки


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

Функция explode()

имеет следующий синтаксис:

list explode(string $token, string $Str [, int $limit])

Она получает строку, заданную в ее втором аргументе, и пытается найти в ней подстроки, равные первому аргументу. Затем по месту вхождения этих подстрок строка "разрезается"

на части, помещаемые в массив-список, который и возвращается. Если задан параметр $limit, то учитываются только первые ($limit-1)

участков "разреза". Таким образом, возвращается список из не более чем $limit элементов. Это позволяет нам проигнорировать возможное наличие разделителя в тексте последнего поля, если мы знаем, что всего полей, скажем, 6 штук. Вот пример:

$st="4597219361|Иванов|Иван|40|ivan@ivanov.com|Текст, содержащий (|)!";

$A=explode("|",$st,6); // Мы знаем, что там только 6 полей!

// теперь $A[0]="Иванов", ... $A[5]= "Текст, содержащий (|)!"

list($Surname,$Name,$Age,$Email,$Tel)=$A; // распределили по переменным

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

Функция implode() и ее синоним join() производят действие, в точности обратное вызову explode().

string implode(string $glue, list $List) или

string join(string $glue, list $List)

Они берут ассоциативный массив (обычно это список) $List, заданный в ее первом параметре, и "склеивают"

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

Рекомендую вам чаще применять функции implode()

и explode(), а не писать самостоятельно их аналоги. Работают они очень быстро.



Списки множественного выбора (multiple)


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

èìÿ=çíà÷åíèå1&èìÿ=çíà÷åíèå2&...&èìÿ=çíà÷åíèåN

Кстати говоря, совершенно не уникальный случай — то, что с одним именем связано сразу несколько значений. Действительно, нам никто не мешает создавать и другие тэги с идентичными именами. Это часто делается, например, для переключателей-флажков:

<input type=checkbox name=èìÿ value="Îäèí">Îäèí<br>

<input type=checkbox name=èìÿ value="Äâà">Äâà<br>

<input type=checkbox name=èìÿ value="Òðè">Òðè<br>

Если теперь пользователь установит сразу все флажки, то сценарию поступит строка (конечно, в URL-кодированном виде):

èìÿ=Îäèí&èìÿ=Äâà&èìÿ=Òðè

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

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



Способ первый: использование auto_prepend_file


Как следует из Приложения 2, PHP опирается при выполнении сценариев на специальный файл конфигурации под названием php.ini, в котором хранится большинство его настроек, заданных в виде директив. Кроме того, если PHP установлен как модуль Apache (а именно так обстоит дело у большинства хостинг-провайдеров), некоторые директивы можно также включать прямо в файлы .htaccess, управляющие работой сервера. Последние могут быть помещены в любой каталог, содержащий сценарии на PHP. Таким образом, для заданного каталога и всех его подкаталогов указанные настройки всегда будут действовать.

Помните, что для помещения директивы PHP с каким-нибудь именем NAME в файл .htaccess

ее нужно назвать php_NAME, а значение отделить от имени не знаком =, как в php.ini, а пробелом. В противном случае Apache будет сообщать о неизвестной директиве в файле конфигурации.

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

Конечно, вставлять директиву auto_prepend_file в глобальный php.ini нет никакого смысла. Ведь у подавляющего большинства хостинг-провайдеров одни и те же Apache и PHP обслуживают сразу несколько виртуальных хостов, принадлежащих разным владельцам. А значит, никто не разрешит вам изменять глобальные настройки интерпретатора. В этом случае модификация файлов .htaccess оказывается единственно правильным и возможным решением. Правда, для этого нам нужно знать, какой физический каталог соответствует на нашем сервере корневому для документов. Выяснить это можно, например, с помощью такого простого сценария:

Листинг 29.4. Определение физического корневого каталога сервера

<?

echo $DOCUMENT_ROOT;

?>

Пусть, к примеру, у нашего хостинг-провайдера используется каталог /home/dk/www. Тогда для автоматического подключения библиотекаря ко всем сценариям на PHP нужно добавить в файл .htaccess примерно такую строку:

php_auto_prepend_file  /home/dk/www/lib/librarian.phl

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

Как уже упоминалось, данный способ не подходит для того виртуального сервера для Windows, установка которого описана в частиII настоящей книги. Изменение php.ini — тоже не очень удачная идея в силу вышеизложенных рассуждений. Тут нам на помощь придет второй способ, который мы сейчас и рассмотрим.



Способ второй: установка обработчика Apache


Установка своего обработчика сопряжена с несколько большими сложностями, чем использование директив auto_prepend_file

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

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



SSI и функция virtual()


Как известно, для одного и того же документа в Apache нельзя применять два "обработчика". Иными словами, действует принцип (по крайней мере, в Apache первого поколения): либо PHP, либо SSI (Server-Side Includes— Включения на стороне сервера). Однако в PHP имеются определенные средства для "эмуляции"

SSI-конструкции include virtual.

Конструкция include virtual

загружает файл, URL которого указан у нее в параметрах, обрабатывает его нужным обработчиком и выводит в браузер.

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

int virtual(string $url)

Функция virtual()

представляет собой процедуру, которая может поддерживаться только в случае, если PHP установлен как модуль Apache. Она делает то же самое, что и SSI-инструкция <!--#include virtual=...-->. Иными словами, она генерирует новый запрос серверу, обрабатываемый им обычным образом, а затем выводит данные в стандартный поток вывода.

Чаще всего функция virtual() используется для запуска внешних CGI-сценариев, написанных на другом языке программирования, или же для обработки SSI-файлов более сложной структуры. В случае, если запускается сценарий, он должен генерировать правильные HTTP-заголовки, иначе будет выведено сообщение об ошибке. Заметьте, что для включения обычных PHP-файлов с участками кода [E85] функция virtual() неприменима — это выполняет оператор include.



Ссылки и интерфейсы


Как мы знаем, в PHP оператор присваивания всегда копирует значения переменных, какой бы сложной структуры они ни были. Это же, напомню, происходит и с объектами. Что тогда получится, если мы скопируем, например, объект класса MysqlTable? Вообще говоря, ничего хорошего. Произойдет дублирование всех свойств и методов объекта. Фактически, мы получим сразу две независимые "обертки"

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

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

методом GetInfo() в специальном свойстве объекта. Действительно, зачем загружать сервер лишней работой по чтению одних и тех же данных, когда можно хранить их в программе и сразу же использовать? Это свойство будет инициализироваться при конструировании объекта класса MysqlTable и обновляться каждый раз при обращении к методу SetInfo().

То есть наше свойство будет представлять собой аналог "зеркала" записи в таблице MySQL, по аналогии с "зеркалами" сайтов в Интернете. Класс MysqlTable

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

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

$t1=new MysqlTable("MyTable");

. . .

function DoIt($t)

{ $t->SetInfo("This is the new info!");

}

. . .


$t=new MysqlTable("MyTableName");

$t->SetInfo("Data");

DoIt($t);

$Inf=$t->GetInfo(); // в $Inf будет строка Data!

Впрочем, в приведенном только что фрагменте это недоразумение можно легко преодолеть, передав функции ссылку на объект:

function DoIt(&$t)

{ $t->SetInfo("This is the new info!");

}

Я намеренно привел здесь пример, когда ограничение на копирование объектов все же можно обойти относительно безболезненно. Настало время описать неразрешимую (во всяком случае, похожим методом) задачу. Но прежде обратите внимание, что в нашем примере объект передается "вглубь"

кода (внутрь функции), а не "наружу"

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

Но обо всем по порядку. Чтобы чуть сгустить краски и не вдаваться в абстрактные рассуждения, давайте предположим, что наш класс MysqlTable вообще не допускает копирования его объектов, а при случайном выполнении такого копирования работает совершенно неправильно. Нужно заметить, что это не так уж и далеко от истины, особенно если мы используем MysqlTable не напрямую, а как базовый для какого-то другого типа (например, для класса форума).

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



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

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

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

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



Как следует из законов Мэрфи, "у любой сложной задачи всегда имеется одно простое, красивое и легкое для понимания… неправильное решение". В нашем случае это будет возврат из функции объекта класса MysqlTable "обычным" способом, подразумевающим копирование. Но ведь, по имеющейся между нами договоренности, объекты этого класса нельзя копировать!


Стек буферов


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

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

Листинг 30.15. Пример "перехвата" выходного потока

<?

ob_start(); // устанавливаем перехват в буфер 1

echo "1"// попадет в 1-й буфер

ob_start(); // откладываем на время буфер 1 и активизируем второй

echo "2";                // текст попадет в буфер 2

$A[2]=ob_get_contents(); // текст во втором буфере

ob_end_clean();          // отключает буфер 2 и активизируем первый

echo "1";                // попадет опять в буфер 1

$A[1]=ob_get_contents(); // текст в первом буфере

ob_end_clean(); // т.к. это последний буфер, буферизация отключается

// Распечатываем значения буферов, которые мы сохранили в массиве

foreach($A as $i=>$t) echo "$i: $t<br>";

// Выводится:

// 2: 2

// 1: 11

?>

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



Степенные функции


float sqrt(float $arg)

Возвращает квадратный корень из аргумента. Если аргумент отрицателен, то генерируется предупреждение, но работа программы не прекращается! Это выглядит довольно странно: интересно, что в этом случае возвращается функцией?..

float log(float $arg)

Возвращает натуральный логарифм аргумента. В случае недопустимого числа печатает предупреждение, но, как и sqrt(), не завершает программу.

float exp(float $arg)

Возвращает e (2,718281828…) в степени $arg.

float pow(float $base, float $exp)

Возвращает $base в степени $exp.



Страница (или HTML-страница)


Адресуемая из Интернета минимальная единица текстовой информации службы World Wide Web, которая может быть затребована у Web-сервера и отображена в браузере. Часто страница представлена отдельным HTML-документом, однако в последнее время число таких страниц постоянно сокращается— чаще они генерируются автоматически "на лету" какой-нибудь программой и тут же отсылаются клиенту. Например, гостевая книга, в который пользователь может оставить текстовое сообщение, — пример страницы, не являющейся HTML-документом в обычном смысле. Язык HTML (Hypertext Markup Language — Язык разметки гипертекста) позволяет вставлять в страницы ссылки на другие страницы. Щелкнув кнопкой мыши на поле ссылки, пользователь может переместиться к тому или иному документу. Впрочем, подразумевается, что читатель более-менее знаком с языком HTML, а потому в этой книге о нем дается минимум сведений — в основном только те, которые касаются форм.



String


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


Обычная строка, или тип, который можно перевести в строку.



Строка в апострофах


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

r    последовательность \' трактуется PHP как апостроф и предназначена для вставки апострофа в строку, заключенную в апострофы;

r    последовательность \\ трактуется как один обратный слэш и позволяет вставлять в строку этот символ.

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



Строка в кавычках


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

r \n обозначает[В. О.31]  символ новой строки;

r    \r обозначает символ возврата каретки;

r    \t обозначает символ табуляции;

r    \$ обозначает символ $, чтобы следующий за ним текст случайно не был интерполирован, как переменная;

r    \" обозначает кавычку;

r    \\ обозначает обратный слэш;

r    \xNN обозначает символ с шестнадцатеричным кодом NN.

Переменные в строках интерполируются. Например:

$a="Hello";

echo "$a world!"

Этот фрагмент выведет Hello world!, т. е. $a в строке была заменена на значение переменной $a (этому поспособствовал знак доллара, предваряющий любую переменную).

Давайте рассмотрим еще один пример.

$a="Hell";  // ñëîâî Hello áåç áóêâû "o"

echo "$ao world!";

Мы ожидаем, что выведется опять та же самая строка. Но задумаемся: как PHP узнает, имели ли мы в виду переменную $a или же переменную $ao? Очевидно, никак. Запустив фрагмент, убеждаемся, что он генерирует сообщение о том, что переменная $ao не определена. Как же быть? А вот как:

$a="Hell";  // ñëîâî Hello áåç áóêâû "o"

echo $a."o world!";  // îäèí ñïîñîá

echo "{$a}o world!"; // äðóãîé ñïîñîá

echo "${a}o world!"; // òðåòèé ñïîñîá!

Мы видим, что существует целых три способа преодолеть проблему. Каким из них воспользоваться — дело ваше. Мне больше нравится вариант с {$a}, хотя он и введен в PHP лишь недавно.

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



Строки


Строки представляют собой массивы символов. Обычно при поиске по текстовым полям по запросу SELECT не берется в рассмотрение регистр символов, т.е. строки "Вася" и "ВАСЯ" считаются одинаковыми. Кроме того, если база данных настроена на автоматическую перекодировку текста при его помещении и извлечении (см. ниже), эти поля будут храниться в указанной вами кодировке.

Для начала давайте ознакомимся с типом строки, которая может хранить не более length символов, где length принадлежит диапазону от 1 до 255.

VARCHAR(length) [BINARY]

При занесении некоторого значения в поле такого типа из него автоматически вырезаются концевые пробелы (как будто по вызову функции rtrim()). Если указан флаг BINARY, то при запросе SELECT строка будет сравниваться с учетом регистра. Тип VARCHAR неудобен тем, что может хранить не более 255 символов. Вместо него я рекомендую использовать другие текстовые типы, перечисленные в табл. 26.3.

Таблица 26.3. Строковые типы данных таблиц MySQL

Тип

Описание

TINYTEXT

Может хранить максимум 255 символов

TEXT

Может хранить не более 65 535 символов

MEDIUMTEXT

Может хранить максимум 16 777 215 символов

LONGTEXT

Может хранить  4 294 967 295 символов

Чаще всего применяется тип TEXT, но если вы не уверены, что данные не будут всегда короче 65 536 байтов, используйте LONGTEXT.

Слухи о том, что TEXT-типы занимают намного больше места, чем аналогичные VARCHAR-ïîëÿ, сильно преувеличены.



Строковые функции


Строки в PHP — одни из самых универсальных объектов. Как мы уже видели, любой, сколь угодно сложный объект можно упаковать в строку при помощи функции Serialize() (и обратно через Unserialize()). Строка может содержать абсолютно любые символы с кодами от 0 до 255 включительно. Нет никакого специального маркера "конца строки", как это сделано в Си (там конец строки помечается символом с нулевым кодом). А значит, длина строки во внутреннем представлении PHP хранится где-то отдельно. Для формирования и вставки непечатаемого символа в строку (например, с кодом 1 или 15) используется функция chr(), которую мы рассмотрим ниже.

Наконец, из-за слабого контроля типов в PHP строка может содержать

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

В этой главе я описываю только самые употребительные и удобные функции (около 80%), пропуская все остальные. Какие-то из не вошедших в данную главу функций (например, quotemeta()) мы будем рассматривать в других главах — там, где это показалось мне наиболее логичным. Так что, не найдя описание интересующей вас функции здесь, подумайте: возможно, оно лучше подходит для другой темы и его лучше поискать там? И, наконец, последней инстанцией для вас, конечно же, должна являться документация PHP.



Строковые операции


r    a.b [В. О.33] — слияние строк a и b

r    a[n]  — символ строки в позиции n

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



Строковые выражения


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

$a="Ýòî òåêñò, íà÷èíàþùèéñÿ íà îäíîé ñòðîêå

è ïðîäîëæàþùèéñÿ íà äðóãîé,

òðåòüåé è ò. ä.";

Я уже много раз использовал в примерах строковые константы, заключенные как в кавычки, так и в апострофы. [В. О.30] Настало время поговорить о том, чем эти представления отличаются.



Связывание PHP с другим расширением


Как мы знаем, сам PHP представляет собой обычный обработчик. Значит, скажете вы, чтобы заставить его обрабатывать документы с расширением, отличным от PHP, нам нужно просто добавить директиву AddHandler для этого расширения в соответствующий файл .htaccess? Не совсем. Проблема заключается в том, что мы не знаем идентификатора обработчика, он хранится где-то в недрах кода интерпретатора. Вместо этого мы поступим по-другому: заставим Apache считать, что документы с нужным нам расширением имеют тот же тип, что и с расширением php.

Что же такое тип документа? Это еще одно понятие, которое использует Apache в своей работе. Некоторые из этих типов также "понимают" и браузеры. В их числе, например, text/html, обозначающий HTML-страницу, image/gif, который сигнализирует, что данные являются рисунком GIF,

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

Однако есть несколько типов документов, которые никогда не отсылаются браузеру в исходном виде. Один из них — application/x-httpd-php. Именно с этим типом и связан интерпретатор PHP. Если сервер "видит", что пользователь запросил страницу, которая имеет тип application/x-httpd-php, он активизирует PHP, а уж тот берет на себя всю дальнейшую ответственность по запуску сценария и выводу "правильного" заголовка типа (чаще всего text/html) в браузер.

Как же сервер узнает, какой тип имеет тот или иной документ? Вообще говоря, это отдельная проблема. Самое простое ее решение — определять тип по расширению файла. В большинстве случаев это оказывается самым лучшим решением. Программист может сам задать, какое расширение соответствует тому или иному типу, добавив в нужный файл .htaccess следующую директиву:

AddType имя_типа расширение1 расширение2 …

А как быть, если многие из наших документов не имеют в принципе никакого расширения? Например, мы хотим хранить рисунки GIF, JPG и PNG в файлах без расширения. Разумеется, в этом случае директива AddType нам не поможет. Однако у Apache существует еще одно мощное средство для распознавания типов страниц — это модуль mod_mime_magic

(конечно, если он подключен к той версии сервера, которая установлена у вашего хостинг-провайдера). В случае, если определение типа на основе директив AddType закончилось неудачей, этот модуль пытается по нескольким первым байтам файла узнать, какого же он типа. Например, во всех GIF-файлах первые три байта — символы G, I и F. Поэтому с вероятностью практически 100% определение типа проходит правильно.

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

AddType application/x-httpd-php

php4

Теперь для всех файлов с расширением php4 будет выполняться то же, что и для php. Кстати говоря, именно такая директива (но для php) записана в главном файле httpd.conf вашего хостинг-провайдера.



Свойства объекта


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

внутри объекта класса, в которой может храниться какое-то значение. Например, в классе таблицы MySQL, которым мы вскоре займемся, имя таблицы задано в виде свойства $TableName.

То есть, грубо говоря, каждый объект-таблица содержит в себе свою собственную переменную $TableName и имеет над ней полный контроль. Какие именно свойства будет иметь любой объект заданного класса, указывается при создании этого класса.

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

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

Объявление свойств задается при помощи ключевого слова var:

var $pName1, $pname2, ...;

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

Займемся теперь вопросом о том, как нам из программы получить доступ к какому-то свойству определенного объекта (например, объекта $Obj, который мы только что создали). Это делается очень просто при помощи операции ->:

// Выводим в браузер значение свойства Name1 объекта $Obj

echo $Obj->Name1;

// Присваиваем значение свойству

$Obj->Name2="PHP Four";

Если какое-то свойство (например, с именем SubObj) объекта само является объектом (что вполне допустимо), нужно использовать две "стре­лочки":

// Выводим значение свойства Property объекта-свойства

// $SubObj объекта $Obj

echo $Obj->SubObj->Property;

Такой синтаксис был придуман из того расчета, чтобы быть максимально простым. Добавлю, что указание объекта $Obj перед стрелкой обязательно по той причине, что каждый объект имеет свой собственный набор свойств. Поэтому-то они и не пересекаются при хранении, а при доступе нужно уточнить объект, свойство которого запрашивается.

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



Так использовать Cookies â ñåññèÿõ или нет?


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

установлен в значение true (именно оно присваивается ему по умолчанию при установке PHP).

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

(да и всех других документов тоже) немного удлинятся, но, думаю, это не так уж и критично. Главное, что сессии будут работать. Только не забудьте удостовериться, что в конфигурационном файле PHP включена опция session.use_trans_sid.

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



Текстовое поле (text)


<input type=text

  name=èìÿ

  [value=çíà÷åíèå]

  [size=ðàçìåð]

  [maxlen=÷èñëî]

>

Создает поле ввода текста размером примерно в size знакомест и максимально допустимой длиной maxlen символов (то есть пользователь сможет ввести в нем не больше этого количества символов).

Не советую, тем не менее, в программе на Си полагаться, что придет не больше maxlen символов и выделять для их получения буфер фиксированного размера. Дело в том, что злоумышленник вполне может запустить ваш сценарий в обход стандартной формы (содержащей "правильный" тэг <input>) и задать большой объем данных, чтобы этот буфер переполнить — известный прием взлома недобросовестно написанных программ.

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



Терминология


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


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

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

Итак, мы имеем выражение и мы имеем строку. Операцию проверки, удовлетворяет ли строка этому выражению (или выражение — строке, как хотите) условимся называть сопоставлением

строки и выражения. Если какая-то часть строки успешно сопоставилась с выражением, мы назовем это совпадением. Например, совпадением от сопоставления выражения "группа букв, окруженная пробелами" к строке "ab cde fgh" будет строка "cde"

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



Типы блокировок


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

операционной системы FreeBSD), я был неприятно удивлен,— настолько все выглядело сложным и нелогичным. Слава Богу, на самом деле ситуация далеко не так плачевна. Сейчас я постараюсь понятным языком разъяснить все, что касается блокировок в языке PHP. Что же из себя представляют понятия исключительная блокировка

и разделяемая блокировка?



Типы переменных


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



Типы полей


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

я буду помечать необязательные элементы.