Самоучитель PHP 4 - Котеров Д. В.
.pdf468 |
Часть V. Приемы программирования на PHP |
var $Fields; // все физические поля таблицы (имя=тип, имя=тип...) var $Error; // текст последней ошибки ("", если нет)
var $JustCreated; // 1, если таблица была создана, а не загружена //*** Внутренние функции
//Упаковывает поля массива в строку, за исключением тех, которые
//сами являются непосредственными полями в базе данных. function _PackFields(&$Hash)
{ $Data=array();
foreach($Hash as $k=>$v) if($k!=DataField) if(!isSet($this->Fields[$k])) $Data[$k]=$v; return Serialize($Data);
}
//Виртуальная функция производного класса вызывается ПЕРЕД любым
//занесением данных в таблицу (добавлением и обновлением). То есть
//она предназначена для "прозрачной" автоматической генерации некоторых
//полей записи (например, времени ее изменения) в производном классе
//перед ее сохранением.
//Можно, к примеру, в таблице держать какую-нибудь дату в формате
//SDN, а "делать вид", что она хранится в обычном представлении
//"дд.мм.гггг".
//Если эта функция возвратит 0, то операция закончится с ошибкой. function PreModify(&$Rec) { return 1; }
//Виртуальная функция вызывается ПОСЛЕ выборки записи из таблицы, а
//также в конце модификации записи. То есть она предназначена для
//"прозрачной" модификации только что полученной из таблицы записи.
//Возвращаясь к предыдущему примеру, мы можем при извлечении записи
//из таблицы STM-поле преобразовать в "дд.мм.гггг", и "никто ничего
//не заметит".
function PostSelect(&$Rec) { return; } // Возвращает имя таблицы
function GetTableName() { return $this->TableName; }
//Возвращает результат запроса select. В дальнейшем этот результат
//(дескриптор) будет, скорее всего, обработан при помощи GetResult().
//$Expr — выражение SQL, по которому будет идти выборка
//$Order — правила сортировки (по умолчанию — по убыванию id) function TableSelectQuery($Expr="",$Order="id desc")
{ $this->Error=""; if(!$Expr) $Expr="1=1";
$r=mysql_query("select * from ".$this->TableName.
Глава 31. Объектно-ориентированное программирование на PHP |
469 |
" where ($Expr) and (id>1) order by $Order"); if(!$r) { $this->Error=mysql_error(); return; } return $r;
}
function SelectQuery($Expr="",$Order="id desc") { return $this->TableSelectQuery($Expr,$Order); }
//Возвращает результат предыдущего запроса select (точнее, очередную
//найденную запись) в виде распакованного (!) массива. Если
//SelectQuery() нашла несколько записей, то, последовательно вызывая
//GetResult(), можно считать их все. Метод делает всю "черную" работу
//по сериализации. Еще раз: если у результата несколько строк, то метод
//возвращает очередную. Если строки кончились, возвращает "".
//Чаще всего в вызове этой функции (и функции SelectQuery) нет
//необходимости — можно воспользоваться методом Select(), который по
//запросу сразу возвращает массив со всеми обработанными результатами! function TableGetResult($r)
{ $this->Error="";
//Выбираем очередную строку в виде массива
if($r) $Result=mysql_fetch_array($r); else $this->Error=mysql_error(); if(!@is_array($Result)) return;
//Перебираем все поля таблицы и записываем их в массив $Hash $Hash=array();
foreach($this->Fields as $k=>$i) if(isSet($Result[$k])) $Hash[$k]=$Result[$k];
//Распаковываем поле с данными
$Hash+=SqlUnpack($Hash[DataField]); unSet($Hash[DataField]); $this->PostSelect($Hash);
// Все сделано return $Hash;
}
function GetResult($r) { return $this->TableGetResult($r); }
//Примечание: мы используем две функции, из которых GetResult()
//просто является синонимом для TableGetResult(), чтобы позволить
//производному классу вызывать функции MysqlTable, даже если они
//переопределены в нем. К сожалению, в PHP это единственный метод
//добиться цели.
//Аналог mysql_num_rows()
function GetNumRows($r) { return mysql_num_rows($r); }
470 |
Часть V. Приемы программирования на PHP |
//Аналог mysql_data_seek(). После вызова этой функции указатель на
//дескриптор $r "перескочит" на найденную запись номер $to, после
//чего GetResult() ее и возвратит.
function DataSeek($r,$to) { return mysql_data_seek($r,$to); }
//Создает или загружает таблицу по имени $Name.
//$Fields — список полей базы. Именно по ним в дальнейшем можно
//будет вести поиск и строить индекс. Кроме того, в запись можно будет
//добавлять ЛЮБЫЕ другие переменные, но они будут сериализованы, а
//потом восстановлены. Формат списка: массив с ключами — именами
//переменных и значениями — их типами. Если $Fields — не массив, то
//считается, что таблица открывается такой, какой она есть. В противном
//случае производится проверка: не добавились или не удалились ли какие-
//то поля или индексы и, если это так, то выполняется соответствующая
//модификация таблицы (кстати, это процесс довольно длительный).
//ВНИМАНИЕ: если в таблице было какое-то поле, которое сериализуется, то
//в будущем при добавлении этого поля к $Fields оно НЕ будет
//автоматически переведено в ранг несущих, т. е. попросту
//пропадет (и наоборот).
//РЕКОМЕНДАЦИЯ: перечисляйте в $Fields те поля, для которых вы ТОЧНО
//уверены, что они будут всегда присутствовать в базе, а также те,
//по которым нужно будет вести поиск, строить индекс и использовать
//distinct.
//$Index — по каким полям нужно строить индекс. Индекс несколько
//увеличивает размер базы, но зато вырастает скорость поиска по ней
//(точнее, по тем полям, для которых используется индекс). Ключи — имена
//столбцов, значения — "размер" индекса (0, если по умолчанию, что чаще
//всего наиболее разумно)
function MysqlTable($Name,$Fields="",$Index="") { $this->TableName=$Name; $this->Error=""; if(is_array($Fields)) {
foreach($Fields as $k=>$v)
if(!eregi("not null",$v)) $Fields[$k]=$v." not null"; $Fields=array("id"=>"int auto_increment primary key") +$Fields+array(DataField=>"mediumblob");
}
Def0($Index,array());
// Считываем из таблицы поле с ее параметрами $this->Fields=array(DataField=>"mediumblob");
Глава 31. Объектно-ориентированное программирование на PHP |
471 |
$Data=$this->TableGetResult(
mysql_query("select ".DataField." from $Name where id=1") );
//Если таблица существует, то запрос окончится успешно.
//В этом случае нужно проверить, не изменилась ли таблица с момента
//последнего обращения, и если это так, то подкорректировать ее. if(@is_array($Data)) {
if(!is_array($Fields)) {
$this->Error="Couldn't create table: no fields specified"; return;
}
Def0($Data["Fields"],array()); Def0($Data["Index"],array());
//** Возможно, что-то изменилось. Тогда выполняем alter table. //1. Добавились поля?
$Lst=array(); foreach($Fields as $k=>$v) {
if(!isSet($Data["Fields"][$k])) $Lst[]="add $k $v"; else if($Data["Fields"][$k]!=$v) $Lst[]="change $k $k $v";
}
//2. Удалились поля? foreach($Data["Fields"] as $k=>$v) if(!isSet($Fields[$k])) $Lst[]="drop $k"; //3. Добавились индексы?
foreach($Index as $k=>$v) if(!isSet($Data["Index"][$k])) $Lst[]="add index index_$k ($k".($v!=0?" ($v)":"").")"; //4. Удалились индексы?
foreach($Data["Index"] as $k=>$v) if(!isSet($Index[$k])) $Lst[]="drop index index_$k"; if(count($Lst)) {
PrintDump($Lst);
if(!mysql_query("alter table $Name ".implode($Lst,","))) { $this->Error=mysql_error();
return;
}
$Changed=1;
}
$this->JustCreated=0; } else {
472 |
Часть V. Приемы программирования на PHP |
//Необходимо создать таблицу.
//BugFix by DM: При создании новой таблицы необходимо очистить
//переменную Error, иначе в ней остается ошибка от попытки
//чтения полей.
$this->Error=""; $Lst=array();
foreach($Fields as $k=>$v) $Lst[]="$k $v"; foreach($Index as $k=>$v)
$Lst[]="index index_$k ($k".($v!=0?" ($v)":"").")"; if(!mysql_query("create table $Name (".implode($Lst,",").")")) { $this->Error=mysql_error();
return;
}
$this->JustCreated=1;
}
// Сохраняем информацию о таблице, если она поменялась if(!empty($Changed)||$this->JustCreated) { $Data["Fields"]=$Fields;
$Data["Index"]=$Index;
Def0($Data["Info"],array()); // Информации не было — делаем пустой $Data=SqlPack($Data);
if($this->JustCreated) {
$Result=mysql_query("insert into $Name(id,".DataField.") values(1,'$Data')");
} else {
$Result=mysql_query("update $Name set ".DataField. "='$Data' where id=1");
}
if(!$Result) { $this->Error=mysql_error(); return; }
}
$this->Fields=$Fields; $this->Index=$Index;
}
//Записывает в таблицу информацию, общую для всей таблицы. Эта
//информация может быть получена потом только при помощи метода
//GetInfo(), и никак иначе. Например, если таблица используется для
//гостевой книги, мы можем сюда записывать какие-нибудь параметры этой
//книги — скажем, имя и пароль владельца. $Inf может быть чем угодно —
Глава 31. Объектно-ориентированное программирование на PHP |
473 |
// даже массивом.
function TableSetInfo($Inf)
{$this->Error="";
//Читаем информационную запись
$r=mysql_query("select ".DataField." from ". $this->TableName." where id=1");
if(!($Data=$this->GetResult($r))) return;
//Устанавливаем поле Info $Data["Info"]=$Inf; $Data=SqlPack($Data);
//Сохраняем результат
if(!mysql_query("update ".$this->TableName. " set ".DataField."='$Data' where id=1")) { $this->Error=mysql_error(); return; } return 1;
}
function SetInfo($Inf) { return $this->TableSetInfo(&$Inf); }
//Возвращает информацию о таблице, ранее занесенную в нее при помощи
//SetInfo. Если информация не была занесена, возвращает пустой массив. function TableGetInfo()
{ $this->Error="";
//Читаем информационную запись
$r=mysql_query("select * from ".$this->TableName." where id=1");
// Если что-то не в порядке, GetResult установит поле Error у объекта if(!($Data=$this->GetResult($r))) return array(); if(!@is_array($Data["Info"])) $Data["Info"]=array();
return $Data["Info"];
}
function GetInfo() { return $this->TableGetInfo(); }
//Уничтожает таблицу. Осторожно! Таблица удаляется без всяких
//предупреждений!!!
function TableDrop() { $this->Error="";
if(!mysql_query("drop table ".$this->TableName)) { $this->Error=mysql_error();
return 0;
}
return 1;
}
474 |
Часть V. Приемы программирования на PHP |
function Drop() { return $this->TableDrop(); }
//Добавляет запись $Rec (обычно это ассоциативный массив с некоторыми
//установленными полями) в таблицу. Автоматически у нее проставляется
//id, а также проверяется, уникальны ли у записи те поля, которые должны
//быть уникальными (указываются в конструкторе). Возвращает 1 в случае
//успеха, при этом в $Rec содержится окончательно сформированная
//запись.
function TableAdd(&$Rec) { $this->Error="";
if(!$this->PreModify($Rec)) return 0;
//Иначе все в порядке. Добавляем запись. $Rec[DataField]=$this->_PackFields($Rec);
//Составляем список имен полей и их значений $LNames=$LVals=array(); foreach($this->Fields as $name=>$type) { $LNames[]=$name; $LVals[]="'".Apostrophs($Rec[$name])."'";
}
$LNames=implode($LNames,","); $LVals=implode($LVals,","); unSet($Rec[DataField]);
//Добавляем
if(!mysql_query("insert into ".$this->TableName. "($LNames) values($LVals)"))
{ $this->Error=mysql_error(); return 0; } $Rec["id"]=mysql_insert_id(); $this->PostSelect($Rec);
return 1;
}
function Add(&$Rec) { return $this->TableAdd(&$Rec); }
//Удаляет из таблицы записи, удовлетворяющие выражению $Expr.
//Например: $Tbl->Delete("(id=$id) or (id=0)");
function TableDelete($Expr) { $this->Error="";
if(!mysql_query("delete from ".$this->TableName. " where ($Expr) and (id>1)"))
{ $this->Error=mysql_error(); return 0; } return 1;
Глава 31. Объектно-ориентированное программирование на PHP |
475 |
}
function Delete($Expr) { return $this->TableDelete($Expr); }
//Возвращает массив записей (ключ — id, значение — запись). В массив
//будет занесено не более $Num записей. Для каждой записи
//вызывается PostSelect()!
function TableSelect($Expr="",$Num=100000,$Order="id desc")
{$this->Error="";
//Выполнить запрос
$r=$this->SelectQuery($Expr,$Order); if(!$r) return 0; // Цикл по найденным записям
for($i=0,$Found=array(); $i<$Num&&($Rec=$this->GetResult($r)); $i++) $Found[$Rec["id"]]=$Rec;
return $Found;
}
function Select($Expr="",$Num=100000,$Order="id desc") { return $this->TableSelect($Expr,$Num,$Order); }
//Обновляет запись в таблице, при этом запись $Upd изменяется и
//становится фактически такой, как она будет выглядеть после обновления.
//То есть к ней могут добавиться новые поля из таблицы. Если записи с
//таким id нет (когда $id не указан в параметрах, его значение берется
//равным $Upd["id"]), то генерируется ошибка!
//Возможно, в записи $Upd не задан идентификатор id (это бывает, если
//мы только что получили данные из формы). В этом случае можно этот
//идентификатор передать через $id.
//Итак, при обновлении id НЕ МЕНЯЕТСЯ по определению (в отличие от
//ДОБАВЛЕНИЯ, когда id всегда проставляется)!
function TableUpdate(&$Upd,$id=0)
{$this->Error="";
//Если задан $id, то устанавливаем в записи этот идентификатор if($id) $Upd["id"]=$id;
//Загружаем старую запись. Она должна быть одна.
$r=$this->SelectQuery("id=".$Upd["id"]); $Rec=$this->GetResult($r);
//Если не удалось, значит, неверное обновление — записи
//еще не существует
if(!$Rec) { $this->Error="NotExists"; return 0; }
//Иначе все в порядке — добавляем. Сначала обновляем
//поля и упаковываем переменные
476 |
Часть V. Приемы программирования на PHP |
$Rec=$Upd+$Rec; $Upd=$Rec; if(!$this->PreModify($Rec)) return 0; $Rec[DataField]=$this->_PackFields($Rec);
//Затем составляем список полей для обновления $Lst=array();
foreach($this->Fields as $name=>$type) $Lst[]="$name='".Apostrophs($Rec[$name])."'"; $Lst=implode($Lst,",");
//Выполняем запрос
if(!mysql_query("update ".$this->TableName. " set $Lst where id=".$Rec["id"]))
{ $this->Error=mysql_error(); return 0; } $this->PostSelect($Rec);
return 1;
}
function Update(&$Upd,$id=0) { return $this->TableUpdate(&$Upd,$id); }
//Возвращает число записей, удовлетворяющих выражению $Expr.
//Если $Expr не задано, возвращает число ВСЕХ записей. function TableGetCount($Expr="")
{ $this->Error=""; if(!$Expr) $Expr="1=1";
$r=mysql_query("select count(if(($Expr) and (id>1),1,NULL)) from ". $this->TableName);
if(!$r) { $this->Error=mysql_error(); return 0; } $a=mysql_fetch_array($r);
return $a[0];
}
function GetCount($Expr="") { return $this->TableGetCount($Expr); }
//Возвращает СПИСОК всех уникальных значений поля $field
//в таблице, удовлетворяющих тому же условию $Expr.
//ВНИМАНИЕ: эта функция работает лишь тогда, когда поле $field
//присутствовало среди полей $Fields при вызове конструктора.
//В противном случае генерируется ошибка.
//Рекомендуется при создании таблицы для поля $field создать индекс. function TableGetDistinct($field,$Expr="")
{ $this->Error=""; if(!$Expr) $Expr="1=1";
$r=mysql_query("select distinct $field from ".
Глава 31. Объектно-ориентированное программирование на PHP |
477 |
$this->TableName." where ($Expr) and (id>1)");
// distinct НЕ работает вместе с order by! Почему — неясно...
if(!$r) { $this->Error=mysql_error(); return 0; } for($Arr=array(),$i=0,$n=mysql_num_rows($r); $i<$n; $i++) $Arr[]=mysql_result($r,$i,0);
return $Arr;
}
function GetDistinct($field,$Expr="")
{ return $this->TableGetDistinct($field,$Expr); } }; // Конец класса
?>
А вот пример применения этого класса (листинг 31.3). Делает он следующее: открывает таблицу в некоторой базе данных (если таблицы с таким именем не существует, создает ее) и добавляет одну пробную запись.
Листинг 31.3. Пример использования класса MysqlTable
<?
include "librarian.phl"; // подключаем библиотекарь
Uses("MysqlTable"); // подключаем модуль с классом таблицы
//Устанавливаем соединение с базой данных mysql_connect("localhost"); mysql_select_db("test");
//Открываем таблицу
$t=new MysqlTable("test",array("t"=>"int")); // Добавляем запись
$d=array("t"=>time()); $t->Add($d);
//Работаем с блоком информации $Inf=$t->GetInfo(); $Inf["a"]=@$Inf["a"]+1; $Inf["b"]=@$Inf["b"]+10;
echo $Inf["a"]," ",$Inf["b"],"<br>"; $t->SetInfo($Inf);
//Выбираем все записи и выводим их $d=$t->Select();
foreach($d as $id=>$Data) { echo "$id: ".$Data['t']."<br>";
