как параметры пользователей, идентификаторы сайтов и любой другой информации, которую могут отправлять пользователи. Информации личного характера здесь нет. В то же время необходимо обеспечить хорошую обработку исключительных ситуа+ ций, с этой целью реализуется интерфейс DataValidation для обработки ошибок (подробнее об этом далее).
Приложение предположительно можно разделить на две подпрограммы в зависи+ мости от направления передачи данных ++++++ в базу данных или из нее. С одной стороны необходимо получать POST+ и GET+информацию с каждого сайта и заставить приложе+ ние точно протоколировать полученные данные. С другой стороны должна быть воз+ можность получать сохраненные данные и выводить их в удобном, читабельном фор+ мате. Все это представляет собой высокоуровневую организацию приложения.
Впримере используются современные методики программирования, а именно ++++++
объектно+ориентированный подход к созданию приложения. PHP5, как известно, хо+ рошо поддерживает объектно+ориентированный принцип, поэтому, реализуя прило+ жение, определенно стоит воспользоваться эффективностью объектов. Одним из значительных преимуществ OO+программирования является возможность создать
спомощью UML визуальное представление намеченного решения (как это делает+ ся, будет показано далее).
Входе фазы тестирования необходимо будет создать целую группу тестов, гаранти+ рующих, что приложение работает предсказуемо и что во время его реального исполь+ зования не возникнут сбои. Часто отладка небольших сценариев сравнительно проста и не должна занимать много времени, но по мере роста размеров и сложности приложе+ ний увеличивается и время, необходимое для отладки. Как уже было сказано, PHPUnit позволяет сократить это время и предоставляет некоторые полезные функции, которые можно использовать для создания структурированного единообразного комплекта тестов.
Далее, следует учитывать то, что может возникнуть необходимость разделить при+ ложение на несколько уровней, иначе говоря, создать многоуровневую архитектуру. Это в дальнейшем способствует отладке и поддержке приложения, потому что слабосвязанные архитектуры позволяют разделить логически обособленный код на модули. Например, Smarty помогает отделить код приложения от уровня представления. С другой стороны, для уровня данных можно создать класс, содержащий множество функций для работы
сбазами данных (аналогичный классу DataManager, который рассматривался в главе 13).
Вкачестве СУБД будет использоваться SQLite, а необходимые базы данных будут созданы с помощью специальных сценариев. Знание структуры базы данных предпо+ лагаемого приложения подталкивает разработчика обдумывать тип необходимого ко+ да, поэтому таблицы баз данных представляют собой хорошую отправную точку для начала разработки приложения.
База данных sitelogs.db
Чтобы спланировать таблицы для разрабатываемого здесь проекта, необходимо сформулировать несколько предположений. Предположим, что существует два сайта для данного проекта ++++++ сайт комиксов и сайт информации об аппаратном обеспече+ нии. При желании количество сайтов можно увеличить. Каждый сайт при регистра+ ции требует от пользователя ответов на несколько вопросов. Типы сайтов, с которых будет собираться информация, не важны ++++++ для создания подходящей базы данных необходимо только знать тип дескрипторов полей для хранения данных. Например, для протоколирования даты доступа используется поле типа date, для хранения имен ++++++ поле строкового типа и т.д.
Учебный пример: диспетчер протоколирования на PHP 643
Естественно, само по себе простое протоколирование персональных сведений о пользователе на центральном сайте не особенно полезно. Вероятнее всего впослед+ ствии необходимо будет узнать, на каких сайтах пользователь регистрировался, когда зарегистрировался, а также получить любые другие сведения по каждому сайту.
База данных sitelogs.db состоит из трех таблиц, в которых будет храниться пользовательская информация, а также вопросы и ответы с различных сайтов. Беспо+ коиться о создании таблиц не стоит ++++++ на сайте издательства представлены сценарии, позволяющие автоматизировать этот процесс. Достаточно лишь определиться, какая информация будет храниться и где она будет храниться.
SQLite поставляется с PHP5 и в этой главе не рассматривается. Если в процессе работы с этой СУБД возникнут какие*либо проблемы, обратитесь к приложению В ‘‘Использование SQLite’’.
Первая таблица называется user_log и создается с помощью следующего SQLite+ запроса:
CREATE TABLE user_log (
user_log_id
integer primary key,
visit_date
date,
visit_time
time,
site_id
int,
demo_id
int,
login_id
string,
session
string,
firstname
string,
lastname
string,
address1
string,
address2
string,
city
string,
state
string,
zip
string
)
В этой таблице содержится необходимая информация по каждому пользователю. Как видно из запроса, протоколируется дата и время посещения сайта, информация сеанса и регистрационная информация и т.д. Следует отметить, что также записыва+ ется идентификатор сайта; он необходим для того, чтобы определить, на какие во+ просы отвечал тот или иной пользователь каждого сайта.
Следующая таблица, user_demographics, создается с помощью такого запроса:
Очевидно, что эта таблица содержит вопросы, задаваемые на каждом сайте. В поле demo_id хранится идентификатор типа вопроса. Например, идентификатор 1 пред+ ставляет вопросы для сайта комиксов, а идентификатор 2 ++++++ вопросы для сайта об ап+ паратном обеспечении. Так как вопросы задаются в определенном порядке, их по+ рядковые номера хранятся в поле seq.
Таблица demographic_description предварительно заполняется необходимы+ ми вопросами. Таблица user_log будет содержать базовую информацию, а ответы будут записываться в таблицу user_demographic и сортироваться по полю seq.
Использование UML для планирования диспетчера протоколирования
Создание диаграмм, описывающих задуманное приложение, ++++++ отличный способ наглядно представить себе это приложение. Все главные аспекты приложения необ+ ходимо обдумать до визуализации различных частей предполагаемого решения с по+ мощью диаграмм классов и диаграмм последовательностей. Диаграммы последова+ тельностей помогают понять движение информационного потока в диспетчере протоколирования от уровня представления к базе данных и обратно.
UML+диаграммы классов помогают понять конструкцию каждого класса, а также взаимосвязь классов. Диаграмма последовательности показывает, как все должно ра+ ботать. Реализация кода будет описана далее.
Планирование классов обработки данных
Для начала предположим, что будут использоваться два главных типа журналов: UserLog и UserDemographic. В первом из них содержится информация об определен+ ном пользователе. База данных хранит информацию, связанную с пользователями. Необходим только способ внутреннего представления этой информации, потому что невозможно просто передать массив запроса непосредственно в базу данных. Строго говоря, это можно сделать, но данные, которые будут сохранены таким способом, скорее всего, окажутся бесполезными.
Наилучший способ представления пользовательской информации ++++++ объект, позво+ ляющий выполнять над данными любые операции, которые могут понадобиться. Мож+ но создать класс для обработки пользовательской информации, выбрав свойства и ме+ тоды, необходимые для получения информации, выполнения некоторых операций (например, проверки достоверности данных) и сохранения информации в базе данных.
Конструктор __construct() можно использовать для инициализации объекта и наполнения массива, который будет использоваться для представления данных. Та+ ким образом, класс UserLog будет иметь следующее свойство:
#contentBase: array
и метод:
+__construct(initdict)
Кроме того, в приложении должна быть возможность сохранять данные в базе, поэтому нужно реализовать соответствующий метод, а также добавить общедоступ+ ный метод persist(). Также необходим способ проверять данные объекта. Для это+ го будет использоваться метод getInvalidData(). На самом деле проверка данных будет реализована с помощью интерфейса, поэтому упомянутый метод будет просто обеспечивать эту функциональность для интерфейса DataValidation.
Учебный пример: диспетчер протоколирования на PHP 645
Говоря об интерфейсах, стоит упомянуть другую операцию, которая должна быть реализована во всех классах обработки данных: отображение информации пользова+ телю. Для этого можно использовать интерфейс DataOutput, в котором объявлена абстрактная функция toHTML(). Это означает, что в классе UserLog должна быть оп+ ределена функция toHTML().
И все же для класса UserLog нужно сделать еще кое+что. Массив наполняется дан+ ными из запроса. Нужно, чтобы этот объект сохранял пользовательскую информацию в базе данных, но очевидно, демографические данные пользователей (ответы) долж+ ны быть частью глобальной переменной $_REQUEST. Определенно нужен способ из+ влечения демографических данных пользователей из этого массива и передачи этих данных объекту UserDemographic, который применяется для их обработки. В дан+ ном случае эту задачу решает конструктор класса.
Поскольку класс UserLog будет принимать всю собранную информацию, класс UserDemogrpahic будет содержаться внутри него. Иначе говоря, объект UserDemographic может быть создан только из класса UserLog. Это имеет смысл, потому что обрабатывать демографические данные пользователя необходимо только во вре+ мя записи его данных. (Не путайте с наследованием ++++++ лучше представлять себе эту ситуацию как существование одного объекта внутри другого объекта.)
Связь между классами UserDemographic и UserLog вынуждает определить
вклассе UserLog метод gatherUserDemographics(). Этот метод будет отвечать за получение уникальной демографической информации из базы данных для использо+ вания в классе Logcontainer (он будет описан далее).
Остается еще один вопрос, касающийся хранения пользовательских журналов: ка+ кое поле необходимо использовать в качестве первичного ключа? Для хранения идентификатора журнала (идентификатор используется в качестве первичного ключа
втаблице user_log) необходимо частное свойство. Поэтому в классе создается част+ ное свойство с именем id с целым значением по умолчанию равным 0.
Демографические данные пользователей также должны сохраняться и обрабатывать+ ся, поэтому необходим класс UserDemographic для выполнения соответствующих опера+ ций. В разрабатываемом проекте класс будет сохранять демографические данные одного пользователя и связанный вопрос. Вопрос проще всего хранить в частном свойстве, но один из самых важных аспектов этого класса заключается в том, что необходимо сохра+ нить связь между пользователем, вопросами и порядком, в котором эти вопросы задаются (очевидно, что информация будет бесполезной, если для одного пользователя из базы данных будут извлекаться ответы другого). Поэтому в класс UserDemographic стоит добавить два общедоступных свойства getId(Id) и setSequence(sequence).
Класс UserDemographic не будет отвечать за сохранение своих данных, посколь+ ку он содержится в классе UserLog, однако это не помешает заставить его генериро+ вать собственные SQL+запросы, которые будет использовать класс UserLog. (Не сле+
дует реализовывать в классе UserLog метод специально для другого класса.) В результате в класс UserDemographics также добавляется функция toSQL().
Естественно, эти два класса также имеют общие функции. Поэтому стоит задумать+ ся над возможностью создать шаблонный класс, свойства и методы которого наследо+ вались бы этими классами. Не осознавая этой возможности, вы не сможете в полной мере реализовать OO+подход. Рассмотрим организацию класса PersistableLog.
Класс PersistableLog будет отвечать за реализацию методов __get(), __set(), getProperty() и setProperty(), а также деструктора __destruct(). По сути это будет базовый класс для двух других классов, которые обсуждались выше.
646 Глава 17
Объекты должны сохранять информацию в нескольких различных форматах. Один тип объектов будет массивом ключей и значений, относящимся к базе данных, другой будет содержать ключи и значения, подлежащие выводу в удобочитаемом для человека формате. В результате используемые объекты будут содержать массив, кото+ рый можно будет применить для вставки значений в базу данных (посредством имен полей), и массив, который можно будет использовать для возвращения удобочитае+ мых для человека данных.
Наконец, рассмотрим класс LogContainer. Как следует из имени этого класса, он предназначен для хранения последовательностей UserLog+объектов. Зачем это нуж+ но? При опросе базы данных должна быть возможность получить группу UserLog+ объектов, созданных на основании определенных критериев. Например, чтобы про+ смотреть все журналы одного сайта, можно создавать объекты класса LogContainer путем получения и хранения всех UserLog+объектов, соответствующих этому сайту.
Внутренне UserLog+объекты должны сохраняться в частном массиве. Кроме того, по+ надобится частное соединения с базой данных, а также два удобных метода getCount() (возвращающий количество журналов, полученных в ходе запроса) и getUserLogs().
UML+диаграмма на рис. 17.2 представляет рассмотренную выше организацию дис+ петчера протоколирования.
DataOutput
DataValidation
PersistableLog
LogContainer
UserLog
UserDemographic
Рис. 17.2.
Учебный пример: диспетчер протоколирования на PHP 647
Еще стоит упомянуть класс LogUtils. Его основные функции для работы с базами данных наследуются описанными выше классами. Класс LogUtils представляет со+ бой пример хорошей практики программирования, поддерживающей абстракции баз данных. Иными словами, чтобы заменить используемую СУБД, необходимо внести всего несколько изменений в этом классе. Класс LogUtils здесь не рассматривается, поскольку он, по сути, не является частью бизнес+логики приложения. Подробнее этот класс обсуждается в разделе описания кода.
Теперь рассмотрим диаграммы классов обработки исключений.
Планирование классов обработки исключений
В диспетчере протоколирования базовый класс обработки исключений MultiLogException расширяет встроенный PHP5+класс Exception в соответствии с ре+ комендациями. Класс хранит сообщение об ошибке в виде частного свойства, значе+ ние которого присваивается в конструкторе __constructor($message). Естествен+ но, этот класс также предоставляет две общедоступные функции suggestedSolutions() и getErrorMessage().
Здесь следует отметить, что функция suggestedSolutions() определяется как абстрактная, поэтому она должна быть реализована в классах+наследниках. В связи с этим стоит рассмотреть диаграмму класса, поскольку существует множество различ+ ных типов исключений, которые должны предоставлять пользователю различные предложения по устранению ошибок.
Кроме этого, для работы со специфическими исключениями, такими как исключе+ ния классов UserLog и LogContainer, исключениями, связанными с некорректным подключением к базе данных, и просто исключительными ситуациями обработки данных, предназначен целый ряд специальных классов обработки исключений. Эти классы показаны на диаграмме (рис. 17.3).
Наконец, чтобы понять, как все это связано вместе, следует изучить диаграмму по+ следовательностей приложения.
Из диаграммы последовательностей диспетчера протоколирования, представлен+ ной на рис. 17.4, должно быть очевидно, что большая часть приложения сосредото+ чена вокруг объекта UserLog. Это именно тот класс UserLog, который отвечает за создание объектов UserDemographic, выполнение запросов и возвращение инфор+ мации из базы данных, а также используется при отправке и получении информации от Smarty+уровня (уровня представления).
Webserver
Database
LogContainer
UserLog
UserDemo
UserDemo
Smarty
userlog"html.tpl
query
"create"
gatherDemographics
query
"create"
"create"
toHTML
toHTML
"create"
this
getDemographics
demographics
toHTML
html
toHTML
html
html
html
html
Рис. 17.4.
Код приложения
Итак, вы уже знаете, как организован диспетчер протоколирования, из чего со+ стоит каждый класс, и хорошо представляете себе, как они связаны вместе. Во многом это самая трудная часть работы. Теперь остается только написать все, что запланиро+ вано. Для начала рассмотрим код главных классов приложения.
Вспомогательные сценарии
Прежде чем двигаться дальше, необходимо упомянуть несколько весьма неболь+ ших сценариев. Эти файлы нельзя назвать главными для функционирования прило+ жения, но их стоит рассмотреть, чтобы избежать путаницы впоследствии.
settings.php
Файл settings.php содержит несколько глобальных значений, к которым при+ ложение должно иметь доступ, и эти значения необходимо изменить, чтобы отразить настройки используемой платформы:
Учебный пример: диспетчер протоколирования на PHP 649
$GLOBALS['dbpath'] = "/var/www/php5/"; // хранить вне
//корневого каталога Web-сайта,
// в конце пути косая черта обязательна $GLOBALS['dbname'] = "sitelogs.db"; $GLOBALS['smarty-path']=$GLOBALS['basepath']."lib/smarty/"; $GLOBALS['maxdemo'] = 1024;
?>
common.php
В файле common.php подключается несколько файлов, которые чаще всего тре+ буются для работы остальных классов:
Сам по себе этот класс не отвечает за инициализацию базы данных. Это делается в файле initialize.php.
initialize.php
Сценарий initialize.php создает используемые в приложении базы данных. Эти базы данных уже обсуждались, поэтому нет необходимости подробно анализиро+ вать сценарий. Сценарий открывает подключение к базе данных, создает первую таб+ лицу user_log и некоторые индексы в ней для ускорения поиска информации:
Вэтой таблице хранятся вопросы (демографические) для каждого сайта, поэтому
еенеобходимо проинициализировать некоторыми данными (в реальной среде разра+ ботчику понадобилось узнать вопросы для сайта и по отдельности добавить их в эту таблицу). Фактически вопросы, размещаемые на определенном сайте, могут даже из+ влекаться из этой базы данных ++++++ иными словами, вопросы формулируются, согласо+ вываются и записываются в базу данных. Когда все вопросы будут готовы, админист+ раторы сайта загрузят их на сайт.
Эта таблица является важнейшей для приложения ++++++ в ней содержатся данные, ко+ торые необходимо связать с ответами, хранящимися в таблице user_demographics. Без этой таблицы невозможно определить, какой ответ соответствует тому или иному вопросу. Примеры вопросов, которые можно задать пользователям обоих сайтов, можно найти в приведенных далее INSERT+операторах:
LogUtils::executeQuery($db,
"CREATE INDEX index_demographic_description_demo_pk ON demographic_description (demo_id, seq)");
Учебный пример: диспетчер протоколирования на PHP 651
LogUtils::executeQuery($db,
"INSERT INTO demographic_description (demo_id, seq, question) VALUES (1, 0, 'Комиксы, приобретенные в течение месяца.' )");
"INSERT INTO demographic_description (demo_id, seq, question)
VALUES (2, 2, 'Общее количество отверток, приобретенное в течение жизни.' )");
LogUtils::executeQuery($db,
"INSERT INTO demographic_description (demo_id, seq, question) VALUES (2, 3, 'Периодичность использования отвертки.' )");
Сценарий завершается закрытием подключения к базе данных:
LogUtils::closeDatabase ($db); ?>
Сценарии обработки данных
Сценарии обработки данных образуют основу приложения и отвечают за всю об+ работку данных начиная с открытия и закрытия подключений к базе данных, преоб+ разования входящей информации в корректный внутренний формат и заканчивая получением и обработкой данных, т.е., по сути, за все приложение. В данном разделе рассматриваются следующие классы:
class.LogUtils.php
class.PersistableLog.php
class.UserLog.php
class.LogContainer.php
class.UserDemographic.php
class.LogUtils.php
Класс class.LogUtils.php содержит большинство функций, позволяющих под+ держивать абстракцию базы данных. Этот класс содержит несколько удобных функ+ ций (которые могут оказаться полезными). Сначала, чтобы реализовать обработку