Самоучитель PHP 4 - Котеров Д. В.
.pdf448 |
Часть 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 нет,
//но мы "ее" реализуем.
