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

PHP5_nachinayushim

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

Учебный пример: диспетчер протоколирования на PHP 663

Следует отметить, что этот класс расширяет класс PersistableLog, поэтому в нем можно использовать некоторые функции PersistableLog. Ниже приведен код конструктора:

function __construct ($initdict) // передается по значению

{

$key = "";

$this->db = LogUtils::openDatabase (); $this->setId(LogUtils::getDef ($initdict["id"],0));

$this->setId(LogUtils::getDef ($initdict["user_log_id"], $this->id)); $this->setProperty ("demo",

LogUtils::getDef ($initdict["demo"], 0), "answer",

LogUtils::getDef ($initdict["question"], "Demographic Answer")); $this->setProperty ("demo",

LogUtils::getDef ($initdict["answer"], $this->demo), "answer",

LogUtils::getDef ($initdict["question"], "Demographic Answer")); $this->question = LogUtils::getDef($initdict["question"], "");

}

Затем определяется функция setId(), используемая в конструкторе:

function setId ($id)

{

$this->setProperty ("id", $id, "user_log_id", "User ID");

}

Порядок следования ответов очень важен:

function setSequence ($seq)

{

$this->setProperty ("seq", $seq, "seq", "Sequence");

}

Следует проверить корректность UserLog+объекта, поэтому далее определяется функция getInvalidDate(). Демографические данные проверять не следует, пото+ му что отсутствие ответа вполне может означать корректный ответ:

function getInvalidData ()

{

$badDataEntries = array();

if ($this->id == null || $this->id <= 0) { array_push ($badDataEntries, "'id' is zero");

}

return $badDataEntries;

}

Наконец, переопределяем функцию toSQL следующим образом:

function toSQL ($tableName = "user_demographics")

{

return LogUtils::generateSqlInsert ($tableName, $this->contentMetaDb, $this->contentBase);

}

664 Глава 17

Сценарии проверки данных и обработки ошибок

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

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

Прежде чем углубляться в код, рассмотрим один из способов использования ин+ терфейса DataValidation в диспетчере протоколирования.

Упомянутый интерфейс реализуется классом class.exceptions.php, который представляет собой базовый класс для всех исключений и обеспечивает несколько различных классов для обработки всевозможных ошибок. Согласно UML+диаграммам, представленным в этой главе ранее, файл class.exceptions.php содержит опреде+ ления следующих классов:

MultiLogException

MultiLogOpenDatabaseException

MultiLogDatabaseQueryException

MultiLogInvalidDatabaseException

MultiLogInvalidDataException

UserLogException

LogContainerException

LogContainerInvalidDataException

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

function suggestedSolutions ()

{

return $this->validatee->getInvalidData();

}

В классе UserDemographic, который уже рассматривался, функция getInvalidData() определена следующим образом:

function getInvalidData ()

{

$badDataEntries = array();

if ($this->id == null || $this->id <= 0) { array_push ($badDataEntries, "'id' is zero");

}

Учебный пример: диспетчер протоколирования на PHP 665

//отсутствие ответа тоже может быть корректным ответом,

//поэтому проверять демографические данные не следует

return $badDataEntries;

}

Конструктор класса UserDemographic содержит следующий оператор:

$this->db = LogUtils::openDatabase ();

Если просмотреть код класса LogUtils, то станет ясно, что он расширяет базовый класс исключений, и это необходимо, потому что функция openDatabase() имеет следующую структуру:

public static function openDatabase ()

{

$db = sqlite_popen($GLOBALS['dbpath'].$GLOBALS['dbname'], 0666, $err); if ( !$db ) {

throw new MultiLogOpenDatabaseException ();

}

return $db;

}

Чтобы понять, как вызывается функция suggestedSolutions() в классе MultiLogOpenDatabaseException, необходимо вспомнить, что этот класс расширяет класс MultiLogException, в котором упомянутая функция объявлена как абстракт+ ная, т.е. любой вызов этой функции должен быть реализован вызывающим классом. В данном случае класс MultiLogException действительно вызывает функцию suggestedSolutions():

function getErrorMessage()

{

$message = "<h3>Ошибка</h3>".$this->message."<br>"; $message .= "<ul>";

foreach ($this->suggestedSolutions() as $solution) { $message .= "<li>".$solution;

}

$message .= "</ul>"; return ($message);

}

}

Приведенных выше примеров должно быть достаточно для понимания некоторых способов использования интерфейсов. Теперь рассмотрим код класса.

В начале следует весьма простое объявление ++++++ в нем нет ничего нового. По суще+ ству, эта функция используется для определения достоверности текущего состояния заданного объекта:

<?

interface DataValidation

{abstract function getInvalidData();

}

?>

Как уже отмечалось, class.exceptions.php представляет собой базовый класс обработки исключений, который наследуется всеми остальными классами, поэтому для начала ничего кроме интерфейса подключать не нужно:

<?

require_once ("interface.DataValidation.php");

666 Глава 17

Первым объявляется класс MultiLogException, который используется многими другими классами обработки исключений. Его необходимо объявить как абстрактный, поскольку в нем реализована одна абстрактная функция. Абстрактная функция ис+ пользуется для того, чтобы заставить подклассы реализовать их собственные функции suggestedSolutions:

abstract class MultiLogException extends Exception

{

protected $message = ""; // каждая исключительная

// ситуация имеет свою причину

/** * Инициализация экземпляра с сообщением об ошибке

*/

function __construct ($msg)

{

$this->message = $msg;

}

abstract function suggestedSolutions ();

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

function getErrorMessage()

{

$message = "<h3>Ошибка</h3>".$this->message."<br>"; $message .= "<ul>";

foreach ($this->suggestedSolutions() as $solution) { $message .= "<li>".$solution;

}

$message .= "</ul>"; return ($message);

}

}

Затем определяется класс MultiLogOpenDatabaseException, расширяющий только что определенный класс MultiLogException. Конструктор этого класса про+ сто передает сообщение родительскому конструктору (в MultiLogException):

class MultiLogOpenDatabaseException extends MultiLogException

{

function __construct ()

{

parent::__construct ("Ошибка при открытии базы данных");

}

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

function suggestedSolutions ()

{

return array ("Запускался ли сценарий инициализации?", "Доступен ли каталог (".$GLOBALS['dbpath'].")

Учебный пример: диспетчер протоколирования на PHP 667

для чтения и записи Web-сервером?", "Доступен ли файл (".$GLOBALS['dbname'].") для чтения и записи Web-сервером?",

"Существует ли база данных и была ли она создана той же версией SQLite, что и в PHP?"

);

}

}

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

class MultiLogDatabaseQueryException extends MultiLogException

{

protected $sql = ""; protected $db = null;

function __construct ($sql = "(no sql supplied)", $db = null)

{

parent::__construct ("Исключение при запросе к базе данных"); $this->sql = $sql;

$this->db = $db;

}

Затем реализуется метод suggestedSolutions(), который использует значения переменных $db и $sql (соединение с базой данных и SQL+запрос соответственно), если они переданы, для вывода некоторых полезных сообщений:

function suggestedSolutions ()

{

$err = array();

if ($this->db) { array_push (

$err, "Ошибка базы данных: ".LogUtils::databaseError ($this->db));

}

array_push ($err, "Ошибка базы данных, ожидалось SQL = ".$this->sql); array_push ($err, "Проверьте права на запись");

array_push ($err, "Проверьте, достаточно ли дискового пространства для работы базы данных");

return $err;

}

}

Следующий класс возвращает ‘‘подсказки’’, если выясняется, что используется не+ корректное соединение с базой данных:

class MultiLogInvalidDatabaseException extends MultiLogException

{

function __construct ()

{

parent::__construct ("Неправильно указанная база данных");

}

function suggestedSolutions ()

{

return array ("Корректно ли открылась база данных?",

668 Глава 17

"Была ли база данных закрыта дважды или сценарий пытается сохранить одни и те же данные дважды?");

}

}

Следующий класс весьма важен, потому что он сообщает причину появления некорректных данных. В этот класс передается объект, реализующий интерфейс DataValidation ++++++ это обязательно, потому что функция getInvalidData() в рас+ сматриваемом классе не реализована. Исключение генерируется до сохранения объекта в базе данных. Как уже было сказано, прежде чем класс PersistableLog запишет информацию в базу данных, выполняется проверка корректности объекта. Если дан+ ные объекта некорректны, то объект передается функции, которая выясняет причину и сообщает о ней:

class MultiLogInvalidDataException extends MultiLogException { private $validatee = null;

function __construct ($v)

{

parent::__construct ("Некорректные данные"); $this->validatee = $v;

}

function suggestedSolutions ()

{ return $this->validatee->getInvalidData();

}

}

Затем определяется несколько специальных классов для обработки UserLog+ исключений. После отображения короткого сообщения об ошибке вызывается метод exitGracefully(), который закрывает соединение с базой данных ++++++ помните, что этот метод определен в PersistableLog, родительском по отношению к UserLog классе:

abstract class UserLogException extends MultiLogException

{

protected $userLog = null;

function __construct ($userLog, $message)

{

parent::__construct ($message);

$this->userLog = $userLog;

$this->userLog->exitGracefully(); // метод определен в PersistableLog

}

}

В классе LogContainer также должна быть реализована обработка исключений, поэтому далее определяется класс, который обрабатывает все UserLog+объекты, соз+ данные в контейнере, и извлекает из них некорректные данные:

abstract class LogContainerException extends MultiLogException

{

protected $logs = null;

function __construct ($logs, $message) { parent::__construct ($message); $this->logs = $logs;

}

function suggestedSolutions ()

{

Учебный пример: диспетчер протоколирования на PHP 669

$solutions = array();

foreach ($this->logs as $log) { $solutions = array_merge (

$solutions, $log.DataValitation::getInvalidData());

}

return $solutions;

}

}

На самом деле генерируемое исключение не сообщает о том, в каком журнале со+ держатся некорректные данные. Для того чтобы это сделать, определяется класс+ наследник LogContainerException:

class LogContainerInvalidDataException extends LogContainerException

{

function __construct ($logs)

{

parent::__construct ($logs, "Объекты UserLog содержат некорректные данные.");

}

}

?>

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

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

Сценарии уровня представления и шаблоны

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

index.php

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

<H3>Настройки</H3>

<UL>

<LI><A HREF="setup.php">Инициализация базы данных</A> (Внимание! Данные будут удалены!)

<LI><A HREF="lib/test.UserLog.php">Запуск комплекта тестирования класса UserLog</A>

670 Глава 17

<LI><A HREF="lib/test.LogContainer.php">Запуск комплекта тестирования класса

LogContainer</A>

<LI><A HREF="lib/test.UserDemographic.php">Запуск комплекта тестирования

класса UserDemographic</A>

<LI><A HREF="post.php">Отправка журналов</A> <LI><A HREF="report.php">Просмотр отчетов</A>

</UL>

Последняя ссылка в коде связана со страницей report.php, которая отвечает за отображение пользовательского интерфейса, где пользователь может выбрать инте+ ресующий его журнал для просмотра.

report.php

В этом сценарии (report.php) используются Smarty+шаблоны, поэтому сначала вместе с остальными необходимыми классами нужно подключить класс Smarty. Если вы используете собственный дистрибутив Smarty, следует скорректировать путь к классу Smarty:

<?

require_once ("settings.php"); require_once ("lib/common.php");

require_once ($GLOBALS["smarty-path"].'Smarty.class.php');

Затем следует создать Smarty+объект и присвоить Smarty+переменной title заго+ ловок страницы:

$smarty = new Smarty;

$smarty->assign ('title', "MultiLog Report");

Затем требуется объявить функцию, которая будет форматировать элементы ‘‘сайт’’ и ‘‘раздел’’ для отображения в выпадающем списке. Для этого создается мас+ сив, а затем вставляется пустая строка для показываемого по умолчанию раздела. Функция arrayNth() применяется для преобразования ассоциативного массива в простой одномерный массив:

function optionMassage (&$a) {

$a = array_pad ($a, count($a)*(-1)-1,""); // увеличиваем массив $a[0][0] = ""; // вставляем пустую строку

$a = LogUtils::arrayNth($a,0); // преобразовываем массив в одномерный, включающий только значения

return $a;

}

Теперь необходимо решить, какой отчет показывать. Сначала отображается report.tpl, поскольку этот шаблон позволяет задать критерии для отбора журналов:

if ($_REQUEST['action'] <> "html") {

// отобразить входные данные $db = LogUtils::openDatabase();

$smarty->assign ('sites', optionMassage(LogUtils::gatherSites($db))); $smarty->assign ('sections', optionMassage(LogUtils::gatherSections($db)));

LogUtils::closeDatabase($db);

$smarty->display ("report.tpl");

Учебный пример: диспетчер протоколирования на PHP 671

После того как пользователь сделал выбор, для отображения результатов запроса используется шаблон report-html.tpl. Для этого сначала создается новый LogContainer+объект, который наполняется значениями, полученными в результате запро+ са, сделанного с помощью шаблона report.tpl. Затем для отображения результатов необходимо передать значения следующему шаблону:

}else {

//отображение результатов

require_once ("lib/class.UserLog.php"); require_once ("lib/class.LogContainer.php");

$logs = new LogContainer ($_REQUEST["start_Year"]. "-".$_REQUEST["start_Month"]."-".$_REQUEST["start_Day"], $_REQUEST["stop_Year"]."-".$_REQUEST["stop_Month"]."-". $_REQUEST["stop_Day"], $_REQUEST["site"], $_REQUEST["section"]);

$smarty->assign ('logs', $logs); $smarty->display ("report-html.tpl");

}

?>

Результаты работы этих сценариев показаны в конце главы, здесь рассмотрены непосредственно файлы шаблонов.

report.tpl

Шаблон report.tpl отображает несколько выпадающих списков, которые мож+ но использовать для создания запроса на выбор и отображение необходимых журна+ лов. Следует отметить, что форма возвращает значение html для параметра action. Это позволяет сценарию report.php определить, какой отчет отображать:

<html><title>MultiLog Report</title> <body>

<h3>{$title}</h3>

<form action="report.php" method="post">

<table>

<tr><td align="right">Начальная дата:</td><td>{html_select_date prefix="start_"

start_year="-2"}</td></tr>

<tr><td align="right">Конечная дата:</td> <td>{html_select_date prefix="stop_"

start_year="-2"}</td></tr>

 

<tr><td align="right">Сайт:</td>

<td><select name=site> {html_options

options=$sites} </select> </td></tr>

 

<tr><td align="right">Раздел:</td>

<td><select name=section> {html_options

options=$sections} </select></td></tr>

<tr><td align="right"></td>

<td><input type="submit" value="Показать

отчет"></td></tr>

 

</table>

<input type="hidden" name="action" value="html"> </form>

</body>

</html>

672 Глава 17

report-html.tpl

Если в базе имеются журналы для отображения, то для вывода их на экран исполь+ зуется шаблон report-html.tpl. Это самый простой шаблон для вывода, но его можно при необходимости расширить. Просто предположим, что шаблон отобража+ ет результаты в таблице и выводит количество показанных журналов. По диаграмме по+ следовательности, которая была представлена выше, можно сделать вывод, что на са+ мом деле запрос выполняется Smarty+шаблоном ++++++ в данном случае к методам toHTML()

и getCount() класса UserDemographic:

<html><title>MultiLog Report</title> <body>

<h3>{$title}</h3>

<table width="2048"><tr><td> {$logs->toHTML ()} </td></tr></table>

<h3>Количество журналов: {$logs->getCount()}</h3>

</body>

</html>

В основном приложение можно считать законченным. В нем имеется вся бизнес+ логика, а также реализована проверка корректности данных и обработка исключе+ ний. Однако это еще не все...

Тестирование приложения

Как узнать, что все действительно работает? Конечно, можно запускать сценарий несколько раз и проанализировать результаты всех попыток. Однако на самом деле не+ обходим более серьезный подход, позволяющий проверить приложение на всех этапах ее работы. Здесь оказывается полезным пакет PHPUnit, который уже обсуждался в этой главе. Ниже рассматривается создание комплекта тестов, которые позволяют прове+ рить, устойчиво ли работает приложение и работает ли оно так, как ожидалось.

Для проверки работы классов UserLog, UserLogContainer и UserDemographic

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

Для проверки работы класса UserLog следует разработать серию из 11 тестов. В этом разделе также рассматривается новая PHPUnit+функция assertEquals(), ко+ торая позволяет проверить несколько условий.

Объявление класса начинается как обычно. Как уже было сказано, любой тестовый класс, для того чтобы сформировать контекст и выполнять другие операции, должен быть подклассом PHPUnit+класса TestCase. Две объявленные в классе TestUserLog переменные служат в качестве контейнеров для тестируемых объектов.

Важно отметить, что тесты получают свои параметры из settings-test.php (отдельный файл настроек), потому что разные тесты для полной сквозной проверки приложения очищают и заполняют разные таблицы базы данных. Кроме того, в чис+ ло подключаемых файлов включается PHPUnit:

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