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

PHP5_nachinayushim

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

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

Другая полезная функция необходима для преобразования N+мерных массивов в одномерные:

public static function arrayNth ($a, $n)

{

$out = array(); foreach ($a as $item) {

$out[$item[$n]]=$item[$n];

}

return $out;

}

Так как данные внутри приложения желательно представлять в виде массивов, следующая функция является особенно полезной. Она принимает запрос и помещает его результат в массив:

public static function queryToMultidimArray ($db, $sql)

{

$out = array();

$query = LogUtils::executeQuery ($db, $sql);

if ( !$query ) {

throw new MultiLogDatabaseQueryException ($sql);

}

while ($row = LogUtils::getQueryArray($query)) { array_push ($out, $row);

}

return $out;

}

В файл для удобства включены две функции. Первая из них возвращает массив всех имеющихся в базе данных сайтов, а вторая возвращает все уникальные разделы (или demo_ids):

public static function gatherSites ($db)

{

return LogUtils::queryToMultidimArray(

$db, "select distinct site_id from user_log order by site_id");

}

public static function gatherSections ($db)

{

return LogUtils::queryToMultidimArray(

$db, "select distinct demo_id from user_log order by demo_id");

}

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

public static function openDatabase ()

{

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

throw new MultiLogOpenDatabaseException ();

}

return $db;

}

654 Глава 17

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

public static function closeDatabase ($db)

{

// вот как это может выглядеть: if ( $db ) {

sqlite_close ($db);

}

}

В следующей функции используется созданная ранее функция implodeQuoted. Она возвращает сгенерированный SQL+оператор:

public static function generateSqlInsert ($tableName, &$metas, &$values)

{

return "insert into ".$tableName.

"(".implode ($metas, ", ")." ) ".

"values(".LogUtils::implodeQuoted($values, ", ")")";

}

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

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

public static function databaseError ($db)

{

$err = "";

if ( $db ) {

$err = "error #".sqlite_last_error($db).

" : ".sqlite_error_string (sqlite_last_error($db));

}

return $err;

}

Код класса завершается определениями нескольких функций, поддерживающий абстракцию баз данных:

public static function executeQuery ($db, $sql)

{

return sqlite_query($sql, $db);

}

public static function getLastInsertedRowId ($db)

{

return sqlite_last_insert_rowid($db);

}

public static function getQueryArray ($query)

{return sqlite_fetch_array ($query);

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

}

}

?>

Теперь можно перейти к изучению класса class.PersistableLog.php, свойства и методы которого наследуются остальными классами.

class.PersistableLog.php

Основная задача файла class.PersistableLog.php заключается в предоставле+ нии инструментов и функций, которые необходимы другим классам для сохранения информации объекта в базе данных. Естественно, этот класс для создания подключе+ ний к базе данных использует средства класса LogUtils (описанного выше) ++++++ это представляет собой один уровень абстракции баз данных.

Сначала создается абстрактный класс (поскольку его объекты создавать не требу+ ется), а также объявляется несколько массивов, которые будут использоваться для хранения метаинформации базы данных об обрабатываемой информации. Иначе го+ воря, массив $contentBase должен содержать фактические значения, которые встав+ ляются в базу данных, а массив $contentMetaDb ++++++ имена полей, в которые вставля+ ются эти значения. Массив $contentMetaHuman содержит значения в удобочитае+ мом формате.

abstract class PersistableLog implements DataValidation

{

protected $contentBase = array(); protected $contentMetaHuman = array(); protected $contentMetaDb = array();

protected $db = null;

Затем объявляется функция __get():

function __get ($key)

{

if (array_key_exists ($key, $this->contentBase)) { return $this->contentBase[$key];

}

return null;

}

Естественно, можно также объявить деструктор для закрытия всех открытых ре+ сурсов:

function __destruct ()

{

$this->exitGracefully();

}

function exitGracefully ()

{

LogUtils::closeDatabase ($this->db);

$this->db = null; // общий способ проверки открытого подключения к базе данных

}

Создается функция setProperty() для установки локального свойства, а также функ+ ции getProperties() и getPropertiesMeta() для возвращения массивов свойств:

656 Глава 17

function setProperty ($key, $value, $metaDb, $metaHuman)

{

$this->contentBase[$key] = $value; $this->contentMetaHuman[$key] = $metaHuman; $this->contentMetaDb[$key] = $metaDb;

}

function getProperties () { return $this->contentBase;

}

function getPropertiesMeta () {

return $this->contentMetaHuman;

}

Теперь с помощью следующей функции формируется SQL+оператор (обратите внимание на использование LogUtils+функции generateSqlInsert()):

function toSQL ($tableName = "user_log")

{

return LogUtils::generateSqlInsert (

$tableName, $this->contentMetaDb, $this->contentBase);

}

Далее начинается бизнес+часть класса. Функция persist() отвечает за вставку информации в базу данных. В случае неудачи она изящно завершает свою работу.

function persist ()

{

$rowid = 0;

// если объект некорректен, то нет смысла продолжать. if (!$this->isValid()) {

throw new MultiLogInvalidDataException ($this);

}

Так как после сохранения данных следует закрыть соединение, сначала необходи+ мо проверить, активно ли оно. Если нет, то:

if ($this->db == null) {

throw new MultiLogInvalidDatabaseException($this);

}

Если все идет как следует, то генерируется необходимый SQL+оператор, после чего соединение закрывается:

if (LogUtils::executeQuery($this->db, $this->toSQL())) { $rowid = LogUtils::getLastInsertedRowId ($this->db); $this->exitGracefully();

} else {

throw new MultiLogDatabaseQueryException ($this->toSQL(), $this->db);

}

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

return $rowid;

}

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

Наконец, определяется функция isValid(), которая используется в persist() ++++++

это булева версия функции getInvalidData():

function isValid ()

{

if (count ($this->getInvalidData()) == 0) { return true;

}

return false;

}

}

?>

class.UserLog.php

Основное назначение класса class.UserLog.php ++++++ связать данные пользова+ тельского журнала с базой данных. Кроме того, здесь связываются ответы с вопроса+ ми определенного сайта. Класс UserLog реализует интерфейс DataValidation и может сам с помощью описанного выше класса PersistableLog сохранять соб+ ранные данные в SQLite+базе данных. Как и остальные классы обработки данных, UserLog для работы с SQLite+соединениями использует класс LogUtils.

Для начала подключаются все необходимые файлы. Следует отметить, что здесь также подключается класс Smarty. Это сделано потому, что одна из функций нагляд+ но показывает, как можно использовать Smarty для вывода данных в HTML+формате. Здесь также используется класс LogUtils, но он подключается посредством файла common.php:

<?

require_once ("common.php");

require_once ("class.PersistableLog.php"); require_once ("class.UserDemographic.php"); require_once ("interface.DataValidation.php"); require_once ('Smarty.class.php');

Затем объявляются необходимые частные переменные и создается конструктор. Назначение конструктора ++++++ связать поступающие данные с внутренними свойствами и именами уровня базы данных. Это возможно, поскольку данные, передаваемые классу UserLog во время создания объекта, организованы в форме массива:

class UserLog extends PersistableLog implements DataValidation

{

private $demographics = array(); private $id = 0;

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

{

$key = "";

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

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

Чтобы ясно представить происходящее, нужно знать, как обрабатывается одно значение. Ниже показано, как устанавливается свойство visit_date ++++++ этот процесс повторяется для всех остальных ключей. Сначала создается имя поля базы данных:

$key="visit_date";

658Глава 17

Спомощью функции setProperty(), содержащейся в классе PersistableLog, ключ связывается с соответствующим значением, а также с удобочитаемым описани+ ем (которое, например, можно выводить на Web+странице). Функция setProperty() имеет следующую форму:

function setProperty ($key, $value, $metaDb, $metaHuman)

Зная об этом, можно понять, что $value в данном случае задается следующим вы+ ражением:

LogUtils::getDef ($initdict[$key], LogUtils::formattedDate())

Известно, что метод getDef() возвращает второй аргумент в случае отсутствия первого. В результате, чтобы обеспечить внутреннее представление данных со значе+ ниями по умолчанию (если реальные значения не были получены с сайта), использу+ ется следующий оператор:

$this->setProperty ($key,

LogUtils::getDef ($initdict[$key], LogUtils::formattedDate()), $key, "Date of visit");

Установка остальных свойств выполняется точно так же. Необходимо лишь обра+ ботать каждое значение и вызвать функцию setProperty():

$key="visit_time"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], LogUtils::formattedTime()), $key, "Time of visit");

$key="site"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], 0), "site_id", "Site ID");

$key="site_id"; $this->setProperty ("site",

LogUtils::getDef ($initdict[$key], $this->site), $key, "Site ID");

$key="section"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], 0), "demo_id", "Section ID");

$key="section_id"; $this->setProperty ("section",

LogUtils::getDef ($initdict["demo_id"], $this->section), "demo_id", "Section ID");

$key="login"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), "login_id","Login ID");

$key="login_id"; $this->setProperty ("login",

LogUtils::getDef ($initdict[$key], $this->login), $key, "Login ID");

$key="session"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), $key, "Session");

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

$key="firstname"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), $key, "First Name");

$key="lastname"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), $key, "Last/Sur Name");

$key="address1"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), $key, "Address Line 1");

$key="address2"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), $key, "Address Line 2");

$key="city"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), $key, "City");

$key="state"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), $key, "State/Province");

$key="zip"; $this->setProperty ($key,

LogUtils::getDef ($initdict[$key], ""), $key, "ZIP/Postal Code");

Прежде чем закончить с конструктором, необходимо обработать ответы (демогра+ фические данные) на вопросы каждого сайта, которые также отправляются в запросе вместе с UserLog+информацией. Для хранения демографических данных имеется от+ дельная таблица, поэтому для создания объектов, которые можно отправлять в эту таблицу, вызывается класс UserDemographics. Количество ответов в запросе зара+ нее не известно, поэтому необходимо использовать цикл до достижения границы массива. Этот цикл реализован в классе class.UserDemographic.php, который подключается в начале class.UserLog.php.

Для каждого ответа (отправленного в запросе в форме demo1=>value1, demo2=>value2 и т.д.) создается новый UserDemographic+объект, а его внутренним свойствам присваиваются значения, отправленные в запросе. (Класс UserDemographic будет рассматриваться далее.)

for ($i=0; $i <= $GLOBALS['maxdemo']; $i++) { $key = 'demo'.$i;

if (array_key_exists($key, $initdict)) { $d = new UserDemographic (

array("id"=>$this->id, "demo"=>$initdict[$key])); array_push ($this->demographics, $d);

} else { break;

}

}

}

660 Глава 17

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

function gatherDemographics ()

{

if (!$this->db) $this->db = LogUtils::openDatabase (); $sql = "SELECT ud.user_log_id AS id, ud.answer AS demo, dd.question AS question FROM user_demographics ud LEFT JOIN user_log ul ON

ud.user_log_id = ul.user_log_id

LEFT OUTER JOIN demographic_description dd ON ( ul.demo_id = dd.demo_id and ud.seq = dd.seq ) WHERE ul.user_log_id =

'".$this->id."' AND ul.site_id = '". $this->site."' AND ul.demo_id =

".$this->section." ORDER BY ud.seq";

$query = LogUtils::executeQuery ($this->db, $sql); if ( !$query ) {

throw new MultiLogDatabaseQueryException ($sql);

}

while ($row = LogUtils::getQueryArray($query)) { $demo = new UserDemographic ($row);

if ($demo->isValid()) {

array_push ($this->demographics, $demo);

}

}

LogUtils::closeDatabase ($this->db);

}

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

function getInvalidData ()

{

$badDataEntries = array(); if ($this->site == 0) {

array_push ($badDataEntries, "'site' is zero");

}

if ($this->section == 0) {

array_push ($badDataEntries, "'section' is zero");

}

if ($this->login == "") {

array_push ($badDataEntries, "'login' is missing");

}

return $badDataEntries;

}

Наконец, определяется собственная функция persist, которая позволяет сохра+ нить UserDemographic+данные в таблице Demographics. Необходимо также связать ее с UserLog+объектами, поэтому требуется доступ к значению поля user_log_id, которое можно получить только после сохранения UserLog+объекта:

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

function persist()

{

// сохранить свои данные

$this->id = parent::persist();

//сохранить демографические данные, порядок имеет значение $i=0;

foreach ($this->demographics as $demo) { $demo->setId($this->id); $demo->setSequence($i++); $demo->persist();

}

}

}

?>

class.LogContainer.php

Имя этого класса ясно дает понять, что он выполняет роль контейнера для UserLog+ объектов. Как обычно, в начале файла подключаются другие необходимые файлы, а затем объявляется класс:

<?php

require_once ("common.php"); require_once ("class.UserLog.php");

class LogContainer

{

private $sql;

private $logs = array(); protected $db = null;

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

function __construct ($start = "", $stop = "", $site = "", $section = "")

{

$initsql = "SELECT

* FROM user_log WHERE 1=1 and";

if($site <> "")

$initsql .= " site_id = '".$site."' and";

if($section <> "")

$initsql .= " demo_id = '".$section."' and";

if($start <> "")

$initsql .= " visit_date >= '".$start."' and";

if($stop <> "")

$initsql .= " visit_date <= '".$stop."'";

Далее проверяется, не заканчивается ли SQL+оператор ключевым словом ‘‘and’’, после чего он используется для опроса базы данных:

// удалить последнее слово "and"?

if (substr($initsql, strlen($initsql)-4, 4) == " and") { $initsql = substr($initsql, 0, strlen($initsql) - 4);

}

// запрос к базе данных

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

$query = LogUtils::executeQuery ($this->db, $initsql);

if ( !$query ) {

throw new MultiLogDatabaseQueryException ($initsql);

}

662 Глава 17

Теперь сценарий проверяет результаты, и если они корректны, то заново связы+ вает их с вопросами. Если результаты некорректны, то они записываются в массив $badLogs, который можно использовать для выдачи сообщения об ошибке (в конце кода конструктора):

$badLogs = array();

while ($row = LogUtils::getQueryArray($query)) { $ul = new UserLog ($row);

if ($ul->isValid()) { $ul->gatherDemographics(); //заново связать

//демографические вопросы с ответами array_push ($this->logs, $ul);

} else {

array_push ($badLogs, $ul);

}

}

LogUtils::closeDatabase ($this->db); if (count ($badLogs) > 0) {

throw new LogContainerInvalidDataException($badLogs);

}

}

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

function getCount ()

{

return count ($this->logs);

}

function toHTML ()

{

$html = '<table border="0" class="LogContainerTable">'; foreach ($this->logs as $ul) {

$html .= $ul->toHTML();

}

$html .= "</table>"; return $html;

}

function getUserLogs () { return $this->logs;

}

}

?>

class.UserDemographic.php

Последний класс для обработки данных в диспетчере протоколирования ++++++

class.UserDemographic.php ++++++ фактически используется исключительно из класса UserLog (потому что вся демографическая информация так или иначе передается в UserLog). Класс хранит один пользовательский ответ, связанный с ним вопрос и UserLog+идентификатор. Ниже приведено начало кода:

<?

require_once ("common.php");

require_once ("class.PersistableLog.php");

class UserDemographic extends PersistableLog

{

private $question = "";

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