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

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

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

418

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

</body></html>

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

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

<foreach src=Book>

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

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

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

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

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

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

419

Генератор данных

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

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

Листинг 30.2. Генератор данных: gbook.php

<?

define("GBook","gbook.dat"); // имя файла с данными гостевой книги

//Загружает гостевую книгу с диска. Возвращает содержание книги. function LoadBook($fname)

{ $f=@fopen("gbook.dat","rb"); if(!$f) return array(); $Book=Unserialize(fread($f,100000)); fclose($f); return $Book;

}

//Сохраняет содержимое книги на диске.

function SaveBook($fname,$Book)

{$f=fopen("gbook.dat","wb"); fwrite($f,Serialize($Book)); fclose($f);

}

//Исполняемая часть сценария.

//Сначала — загрузка гостевой книги. $Book=LoadBook(GBook);

//Обработка формы, если сценарий вызван через нее.

//Если сценарий запущен после нажатия кнопки Добавить...

if(!empty($doAdd)) {

//Добавить в книгу запись пользователя — она у нас хранится

//в массиве $New, см. форму в шаблоне. Запись добавляется,

//как водится, в начало книги.

$Book=array(time()=>$New)+$Book;

420

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

// Записать книгу на диск. SaveBook(GBook,$Book);

}

//Все. Теперь у нас в $Book хранится содержимое книги в формате:

//array (

//время_добавления => array(

//(или id) name => имя_пользователя,

//text => текст_пользователя

//),

//. . .

//);

//Вот теперь загружаем шаблон страницы.

include "gbook.htm"; ?>

Как видим, исполняемая часть довольно небольшая и, действительно, занимается лишь подготовкой данных для их последующего вывода в шаблоне. Шаблон рассматривается этой составляющей как обычный PHP-файл, который она подключает при помощи инструкции include. Ясно, что весь код шаблона (хотя его и очень мало) выполнится в том же контексте, что и генератор данных, а значит, ему будет доступна переменная $Book.

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

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

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

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

421

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

Взаимодействие генератора данных и шаблона

Вернемся опять к тому же генератору данных. В нем мы проверяем, не запущен ли сценарий книги в ответ на нажатие кнопки Добавить в форме. Тут я хочу кое-что напомнить. Если вызвать программу без параметров, то пользователю будет просто выдано содержимое гостевой книги, в противном же случае (то есть при запуске из формы) осуществится добавление записи. Таким образом, мы "одним махом убиваем двух зайцев": используем один и тот же шаблон для двух разных страниц, внешне крайне похожих. Такую практику нужно только приветствовать, не правда ли? Определяем мы, нужно ли добавлять запись, по состоянию переменной $doAdd. Помните, именно такое имя имеет submit-кнопка в форме? Когда ее нажимают, сценарию поступает пара "doAdd=Добавить!", чем мы и воспользовались. Итак, если кнопка нажата, то мы вставляем запись в начало массива $Book и сохраняем его на диске.

Обратите внимание, насколько проста операция добавления записи. Так получилось вследствие того, что мы предусмотрительно дали полям формы с названием и текстом имена, соответственно, New[name] и New[text], которые PHP преобразовал в массив. Вообще говоря, придумывание таких имен для полей — задача как раз того "третьего лица", о котором я говорил выше. Это — работа скорее программистская, нежели дизайнерская (хотя, безусловно, от удачного планирования названий имен полей зависит не так уж и мало).

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

С другой стороны, шаблон gbook.htm не делает никаких предположений о том, как же именно хранится книга на диске и как она обрабатывается. Его дело — "красиво" вывести содержимое массива $Book, "и точка". К тому же он почти не содержит кода на PHP (разве что самый минимум, без которого никак не обойтись). А значит, дизайнеру будет легко изменять внешний вид книги.

Недостатки

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

422

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

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

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

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

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

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

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

423

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

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

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

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

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

Теперь шаблон сам вызывает генератор, который предоставляет ему нужные данные, а заодно и реагирует на запросы пользователя. Он выполняет это, например, при помощи все той же инструкции 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 размещалась где-нибудь в середине шаблона, мы не смогли бы этого сделать, поскольку часть страницы могла быть уже отослана пользователю.

424

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

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

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

AddHandler application/x-httpd-php .html

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

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

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

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

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

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

425

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

Генератор

Пользователь

данных

 

Шаблон

страницы

Рис. 30.1. Двухуровневая схема

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

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

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

426

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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Генератор данных

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Шаблон

 

 

 

Интерфейсная

 

 

 

 

 

 

 

 

часть

 

 

 

Ядро

 

 

 

 

 

 

 

 

 

 

 

страницы

 

 

 

программы

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Конфигу-

 

 

 

 

 

 

 

 

 

 

 

 

рация

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Пользователь

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

Интерфейс

Как можно заметить из листинга 30.4, интерфейс сценария гостевой книги стал гораздо проще, чем это было с генератором данных из листинга 30.2. Файл, в котором содержится его код, называется точно так же, как и файл генератора. Это и не удивительно: "снаружи" интерфейс выглядит как полноценный генератор данных, а о существовании ядра шаблон даже и не "подозревает".

Листинг 30.4. Интерфейс: gbook.php

<?

include "kernel.php"; // Загружаем ядро. $Book=LoadBook(GBook); // Загрузка гостевой книги. // Обработка формы, если сценарий запущен через нее. if(!empty($doAdd)) {

//Добавить в книгу запись пользователя. $Book=array(time()=>$New)+$Book;

//Записать книгу на диск. SaveBook(GBook,$Book);

}

//Загрузка шаблона не нужна — теперь, наоборот, шаблон

//вызывает интерфейс.

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

427

?>

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

Ядро

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

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

(листинг 30.5).

Листинг 30.5. Ядро: kernel.php

<?

//Загружаем конфигурацию. include "config.php";

//Загружает гостевую книгу с диска. Возвращает содержимое книги. function LoadBook($fname)

{ $f=@fopen("gbook.dat","rb"); if(!$f) return array(); $Book=Unserialize(fread($f,100000)); fclose($f);

return $Book;

}

//Сохраняет данные книги на диске.

function SaveBook($fname,$Book)

{$f=fopen("gbook.dat","wb"); fwrite($f,Serialize($Book)); fclose($f);

}

?>