Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Самоучитель PHP 4 - Котеров Д. В.

.pdf
Скачиваний:
99
Добавлен:
24.05.2014
Размер:
4 Мб
Скачать
☆

448

Часть V. Приемы программирования на PHP

//Запускает все фильтры для блока. function _ProcessContent($name,&$cont,$url) { foreach($GLOBALS["BLOCKFILTERS"] as $F)

$F($name,$cont,$url);

}

//"Склеивание" блоков.

//Если тело блока начинается с [name], то оно не просто

//записывается в массив блоков, а "пристыковывается" к значению,

//уже там находящемуся, причем в качестве символа-соединителя

//выступает тело блока с именем name. Если строка name не задана

//(то есть указаны []), используется блок с именем DefaultGlue,

//а если этого блока нет, то соединитель по умолчанию — " | ". function _FBlkGlue($name,&$cont,$url)

{ global $BLOCK; if(ereg("^\\[([^]])*]",$cont,$P)) {

$c=substr($cont,strlen($P[0])); // тело блока после [name]

$n=$P[1];

// имя соединителя

//Есть с чем "склеивать"? if(!empty($BLOCK[$name])) {

$glue=@$BLOCK[$n];

if(!Isset($glue)) $glue=@$BLOCK[BlkDefGlue]; if(!Isset($glue)) $glue=DefGlue; $cont=$BLOCK[$name].$glue.$c;

}

//"Склеивать" нечего — просто присваиваем. else $cont=$c;

}

}

//Удаление начальных символов табуляции из тела блока.

//Теперь можно выравнивать HTML-код в документах с помощью табуляции.

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

//например, в таком контексте:

//< ?foreach($Book as $k=>$v) {? >

//<tr>

//<td>< ?=$Book['name']? ></td>

//<td>< ?=$Book['text']? ></td>

Глава 30. Код и шаблон страницы

449

//</tr>

//< ?}? >

function _FBlkTabs($name,&$cont,$url)

{// используем регулярное выражение в формате PCRE, т. к. это — // единственный приемлемый способ решения задачи

$cont=preg_replace("/^\t+/m","",$cont);

}

?>

"Перехват" выходного потока

В коде листинга 30.14 есть всего лишь несколько вызовов стандартных функций, которые мы еще не рассматривали в этой книге. Я имею в виду функции с префиксами ob_ (от Output Buffering — Буферизация вывода). Их задача — "перехватить" тот текст, который выводится операторами echo, а также участками, расположенными вне PHP-тэгов <? и ?>, и направить его в строковую переменную для дальнейшей обработки.

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

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

void ob_start()

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

string ob_get_contents()

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

450

 

Часть V. Приемы программирования на PHP

 

 

 

 

 

 

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

void ob_end_clean()

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

void ob_end_flush()

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

Стек буферов

Необходимо сделать несколько замечаний насчет функций "перехвата" выходного потока программы. Что получится, если больше одного раза подряд вызвать 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();

// текст в первом буфере

Глава 30. Код и шаблон страницы

451

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

//Распечатываем значения буферов, которые мы сохранили в массиве foreach($A as $i=>$t) echo "$i: $t<br>";

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

//2: 2

//1: 11

?>

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

Проблемы с отладкой

В последней версии PHP на момент написания этих строк имелось небольшое неудобство, которое может превратить отладку программ, использующих буферизацию, в сущий ад. Дело в том, что при включенной буферизации все предупреждения, в нормальном состоянии генерируемые PHP, записываются в буфер, а потому (если программа не отправляет буфер в браузер) могут потеряться. К счастью, это касается лишь предупреждений, которые не завершают работу сценария немедленно. Фатальные ошибки отправляются в браузер почти всегда. Неприятность как раз и состоит в этом "почти". Даже фатальные ошибки останутся программистом незамеченными, если он вызывает функцию ob_start() вложенным образом, — т. е. более одного раза, как это было описано в предыдущем абзаце. Например, если в листинге 30.15 после присваивания текста элементу массива $A[2] вставить вызов несуществующей функции, программа сразу же выдаст пользователю текущее содержимое буфера номер 1, а затем, "не сказав ни слова", завершится. Это и понятно: ведь сообщение об ошибке попало во второй буфер, а значит, было проигнорировано! Почему разработчики PHP, вопреки общеизвестной практике, не разделили стандартный выходной поток и поток ошибок, остается неясным.

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

452

Часть V. Приемы программирования на PHP

Глава 31

Объектно-ориентированное программирование на PHP

В последние 10 лет идея объектно-ориентированного программирования (ООП),

кардинально новая идеология написания программ, все более занимает умы программистов. И это неудивительно. В самом деле, сейчас происходит (а точнее, уже произошло, особенно после выхода стандарта на С++ от 98-го года и изобретения таких языков, как Java и Delphi) примерно то же, что произошло в начале 80-х годов при появлении идеи структурного программирования.

Объектно-ориентированные программы более просты и мобильны, их легче модифицировать и сопровождать, чем их "традиционных" собратьев. Кроме того, похоже, сама идея объектной ориентированности при грамотном ее использовании позволяет программе быть даже более защищенной от различного рода ошибок, чем это задумывал программист в момент работы над ней. Однако ничего не дается даром: сами идеи ООП довольно трудны для восприятия "с нуля", поэтому до сих пор очень большое количество программ (различные системы Unix, Apache, Perl, да и сам PHP) все еще пишутся на старом добром "объектно-неориентированном" Си. Что ж, очень жаль. Ощущение жалости усиливается, если посмотреть на исходные тексты этих программ, поражающие своей многословностью...

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

В этой главе я кратко изложу основные идеи ООП, подкрепляя их иллюстрациями программ на PHP. Конечно, данная глава ни в коей мере не претендует на звание учебника по ООП. Интересующимся читателям рекомендую изучить любой из монументальных трудов Бьерна Страуструпа, изобретателя языка C++.

Классы и объекты

Ключевым понятием ООП является класс. Класс — это просто тип переменной. Ну, не совсем просто... На самом деле переменная класса (далее будем ее называть объ-

454

Часть V. Приемы программирования на PHP

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

В языке C++ мы могли бы, действительно, объявить тип int именно таким образом. Однако в PHP дело обстоит немного хуже: мы не имеем права переопределять стандартные операции (сложение, вычитание и т. д.) для объектов. Например, если бы мы захотели добавить в язык комплексные числа, в C++ это можно было сделать без особых затруднений (и класс комплексных чисел по использованию практически не отличался бы от встроенного типа int), однако в PHP у нас такое добавление не удастся. Альтернативное решение состоит в том, чтобы везде вместо + и других операций использовать вызовы соответствующих функций — например, Add(), которые бы являлись методами класса.

Но обо всем по порядку. Давайте посмотрим, как создать класс в PHP. Это довольно несложно:

class MyName {

описания свойств

. . .

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

}

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

Чтобы создать объект класса MyName, в PHP нужно воспользоваться специальным оператором new:

$Obj = new MyName;

Вот теперь в программе существует объект $Obj, который "ведет себя" так же, как и все остальные объекты класса MyName.

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

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

Глава 31. Объектно-ориентированное программирование на PHP

455

 

 

 

 

 

 

 

 

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

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

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

var $pName1, $pname2, ...;

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

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

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

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

$Obj->Name2="PHP Four";

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

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

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

echo $Obj->SubObj->Property;

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

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

456

Часть V. Приемы программирования на PHP

Методы

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

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

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

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

Класс таблицы MySQL

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

сlass MyClass {

. . .

function Method(параметры) { . . .

}

. . .

}

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

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

Глава 31. Объектно-ориентированное программирование на PHP

457

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

Что бы еще хотелось знать об объекте-таблице? Конечно, имена и типы ее полей. Поместим их в свойство-массив. Наконец, в процессе работы наверняка иногда будут возникать ошибки. Чтобы как-то сигнализировать о них, предлагаю в класс-таблицу ввести еще одно свойство — 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 нет,

//но мы "ее" реализуем.