PHP5_nachinayushim
.pdfУчебный пример: диспетчер протоколирования на 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, создается с помощью такого запроса:
CREATE TABLE user_demographics ( user_log_id integer,
seq |
int, |
answer |
string, |
primary key (user_log_id, seq)
)
В этой таблице содержатся ответы пользователей на вопросы сайта. Данная таб+ лица связана с таблицей user_log по полю user_log_id.
Последняя таблица называется demographic_description и создается с помо+ щью такого запроса:
CREATE TABLE demographic_description ( demo_id int,
seq int, question string,
primary key (demo_id, seq)
)
Учебный пример: диспетчер протоколирования на 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(). По сути это будет базовый класс для двух других классов, которые обсуждались выше.
648 Глава 17
Диаграмма последовательностей диспетчера протоколирования
Из диаграммы последовательностей диспетчера протоколирования, представлен+ ной на рис. 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['basepath'] = "/var/www/php5/"; $GLOBALS['baseurl'] = "http://localhost/php5/";
$GLOBALS['dbpath'] = "/var/www/php5/"; // хранить вне
//корневого каталога Web-сайта,
// в конце пути косая черта обязательна $GLOBALS['dbname'] = "sitelogs.db"; $GLOBALS['smarty-path']=$GLOBALS['basepath']."lib/smarty/"; $GLOBALS['maxdemo'] = 1024;
?>
common.php
В файле common.php подключается несколько файлов, которые чаще всего тре+ буются для работы остальных классов:
<?
require_once ("class.exceptions.php"); require_once ("class.LogUtils.php"); ?>
setup.php
Файл setup.php отвечает за инициализацию базы данных и в случае неудачи воз+ вращает одно из нестандартных сообщений об ошибках:
<?
require_once ("settings.php"); require_once ("lib/common.php");
try {
// инициализация базы данных require_once ("logs/initialize.php");
} catch (MultiLogException $e) {
print "<h3>Произошла ошибка.</h3>"; print $e->getErrorMessage();
}
?> <h3>Готово</h3>
Сам по себе этот класс не отвечает за инициализацию базы данных. Это делается в файле initialize.php.
initialize.php
Сценарий initialize.php создает используемые в приложении базы данных. Эти базы данных уже обсуждались, поэтому нет необходимости подробно анализиро+ вать сценарий. Сценарий открывает подключение к базе данных, создает первую таб+ лицу user_log и некоторые индексы в ней для ускорения поиска информации:
<?
$db = LogUtils::openDatabase ();
LogUtils::executeQuery($db, "drop table user_log"); LogUtils::executeQuery($db, "
CREATE TABLE user_log (
user_log_id integer primary key, visit_date date,
visit_time time, site_id int, demo_id int,
Учебный пример: диспетчер протоколирования на PHP 651
LogUtils::executeQuery($db,
"INSERT INTO demographic_description (demo_id, seq, question) VALUES (1, 0, 'Комиксы, приобретенные в течение месяца.' )");
LogUtils::executeQuery($db,
"INSERT INTO demographic_description (demo_id, seq, question) VALUES (1, 1, 'Названия' )");
LogUtils::executeQuery($db,
"INSERT INTO demographic_description (demo_id, seq, question) VALUES (1, 2, 'Цены' )");
LogUtils::executeQuery($db,
"INSERT INTO demographic_description (demo_id, seq, question) VALUES (1, 3, 'Кредит' )");
LogUtils::executeQuery($db,
"INSERT INTO demographic_description (demo_id, seq, question)
VALUES (2, 0, 'В каком возрасте Вы впервые воспользовались отверткой?' )");
LogUtils::executeQuery($db,
"INSERT INTO demographic_description (demo_id, seq, question) VALUES (2, 1, 'Средняя периодичность приобретения отверток.' )");
LogUtils::executeQuery($db,
"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 содержит большинство функций, позволяющих под+ держивать абстракцию базы данных. Этот класс содержит несколько удобных функ+ ций (которые могут оказаться полезными). Сначала, чтобы реализовать обработку
652 Глава 17
ошибок, подключается класс Exceptions. Затем класс объявляется как абстрактный, поскольку сам по себе он ничего не должен делать ++++++ он лишь содержит методы, не+ обходимые другим классам, поэтому создавать объекты этого класса не нужно:
<?
require_once ("class.exceptions.php");
abstract class LogUtils
{
Код класса начинается с объявления нескольких полезных методов, которые ис+ пользуются другими классами. Например, следующий метод возвращает стандартное значение, в случае если источник отсутствует :
public static function getDef (&$source, $default)
{
if (!empty ($source)) return $source; return $default;
}
Этот метод, в частности, используется в конструкторе класса UserLog для при+ своения значения user_log_id по умолчанию:
$this->id = LogUtils::getDef ($initdict["user_log_id"], 0);
Следующая полезная функция работает так же, как PHP+функция implode, в отли+ чие от последней она заключает переданные ей строки в кавычки, а числа оставляет без кавычек:
public static function implodeQuoted (&$values, $delimiter)
{
$sql = ""; $flagIsFirst = true;
foreach ($values as $value) { if ($flagIsFirst) {
$flagIsFirst = false; } else {
$sql .= $delimiter;
}
if (gettype ($value) == "string") { $sql .= "'".$value."'";
} else {
$sql .= $value;
}
}
return $sql;
}
Эта функция впоследствии используется функцией generateSQLInsert() для форматирования запросов.
Затем объявляется две функции для работы с датами и временем:
public static function formattedDate ()
{
return substr(date("c"), 0, 10);
}
public static function formattedTime ()
{
return substr(date("c"), 11, 8);
}