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

PHP5_nachinayushim

.pdf
Скачиваний:
29
Добавлен:
20.03.2015
Размер:
26.79 Mб
Скачать

Учебный пример: диспетчер протоколирования на 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)

)

644 Глава 17

Очевидно, что эта таблица содержит вопросы, задаваемые на каждом сайте. В поле 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).

Наконец, чтобы понять, как все это связано вместе, следует изучить диаграмму по+ следовательностей приложения.

Exception

MultilogException

MultilogDatabaseException

MultiLogDatabaseQueryException

UserLogException

MultiLogInvalidDatabaseException

MultiLogInvalidDatabaseException

LogContainerException

LogContainerDataException

Рис. 17.3.

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,

650 Глава 17

login_id

string,

session

string,

firstname

string,

lastname

string,

address1

string,

address2

string,

city

string,

state

string,

zip

string

)");

 

LogUtils::executeQuery($db,

"CREATE INDEX index_user_log_site ON user_log (site_id)"); LogUtils::executeQuery($db,

"CREATE INDEX index_user_log_demo ON user_log (demo_id)"); LogUtils::executeQuery($db,

"CREATE INDEX index_user_log_login ON user_log (login_id);"); LogUtils::executeQuery($db,

"CREATE INDEX index_user_log_date ON user_log (visit_date);");

Затем создается таблица user_demographics:

LogUtils::executeQuery($db, "drop table user_demographics"); LogUtils::executeQuery($db, "

CREATE TABLE user_demographics ( user_log_id integer,

seq

int,

answer

string,

primary key (user_log_id, seq) )");

Таблица demographic_description создается несколько иначе, поскольку в ней не будет содержаться информация, полученная с сайта:

LogUtils::executeQuery($db, "drop table demographic_description");

LogUtils::executeQuery($db, "

CREATE TABLE demographic_description ( demo_id int,

seq int, question string,

primary key (demo_id, seq) )");

Вэтой таблице хранятся вопросы (демографические) для каждого сайта, поэтому

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

Эта таблица является важнейшей для приложения ++++++ в ней содержатся данные, ко+ торые необходимо связать с ответами, хранящимися в таблице 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, 'Комиксы, приобретенные в течение месяца.' )");

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);

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]