![](/user_photo/2706_HbeT2.jpg)
PHP5_nachinayushim
.pdf![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u651x1.jpg)
Учебный пример: диспетчер протоколирования на 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;
}
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u652x1.jpg)
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);
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u653x1.jpg)
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u654x1.jpg)
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;
}
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u655x1.jpg)
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u656x1.jpg)
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");
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u657x1.jpg)
Учебный пример: диспетчер протоколирования на 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;
}
}
}
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u658x1.jpg)
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+объекта:
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u659x1.jpg)
Учебный пример: диспетчер протоколирования на 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);
}
![](/html/2706/356/html_7o_ZwAhYkF.IEn3/htmlconvd-YZt_5u660x1.jpg)
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 = "";