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

Самоучитель по PHP 4

.pdf
Скачиваний:
61
Добавлен:
02.05.2014
Размер:
4.36 Mб
Скачать

Глава 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>";

478

Часть V. Приемы программирования на PHP

}

?>

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

Копирование объектов

Так уж устроен PHP, что в нем все переменные, в том числе и объекты (а что такое объект, как не переменная определенного класса?), всегда рассматриваются как простой набор значений и копируются целиком. Например, если у нас есть громадный массив $A и мы выполняем оператор $B=$A, то все содержимое $A будет скопировано в $B один-в-один. Возможно, это как раз то, что и требуется, но вот с объектами сложных классов все обстоит совсем иначе. Предположим, например, что мы выполнили команды:

$Obj1=new MysqlTable("test"); $Obj2=$Obj1;

$Obj1->Drop();

Объект-таблица $Obj1 благополучно уничтожится и пометит в своих свойствах, что он уничтожен, и больше использоваться не должен, но вот $Obj2 об этом и не "догадается". $Obj2 по-прежнему будет "считать", что он — "единственный и неповторимый" объект, привязанный к существующей таблице test, и будет честно пытаться выполнить с ней какие-то операции по запросам.

Этого, к сожалению, нельзя избежать в PHP. А именно, мы не можем никак контролировать процесс копирования объектов. И в этом — безусловная слабость PHP. Так что будьте особенно бдительны.

Ссылки и интерфейсы

Как мы знаем, в PHP оператор присваивания всегда копирует значения переменных, какой бы сложной структуры они ни были. Это же, напомню, происходит и с объектами. Что тогда получится, если мы скопируем, например, объект класса MysqlTable? Вообще говоря, ничего хорошего. Произойдет дублирование всех свойств и методов объекта. Фактически, мы получим сразу две независимые "обертки" для одной и той же таблицы MySQL. Таким образом, изменения, внесенные в первый объект, никак не повлияют на второй, и наоборот.

Я специально проектировал класс MysqlTable так, что даже после копирования объектов этого типа не происходило никаких фатальных недоразумений описанного выше рода. Однако так можно сделать далеко не всегда. Представьте, например, что

Глава 31. Объектно-ориентированное программирование на PHP

479

нам приходится очень часто использовать функцию GetInfo() и довольно редко — SetInfo(). Так как GetInfo() при каждом запросе обращается к MySQL, мы можем получить здесь ощутимый проигрыш в быстродействии. Очевидное решение заключается в промежуточном хранении данных, возвращаемых нашим "обычным" методом GetInfo() в специальном свойстве объекта. Действительно, зачем загружать сервер лишней работой по чтению одних и тех же данных, когда можно хранить их в программе и сразу же использовать? Это свойство будет инициализироваться при конструировании объекта класса MysqlTable и обновляться каждый раз при обращении к методу SetInfo().

То есть наше свойство будет представлять собой аналог "зеркала" записи в таблице MySQL, по аналогии с "зеркалами" сайтов в Интернете. Класс MysqlTable должен следить за тем, чтобы оно всегда содержало актуальные данные те же самые, что и в реальной таблице.

Но, к сожалению, описанная схема не может быть реализована в PHP напрямую, и именно по причине обязательного полного копирования переменных. Вот пример, который породит ошибку:

$t1=new MysqlTable("MyTable");

. . .

function DoIt($t)

{ $t->SetInfo("This is the new info!");

}

. . .

$t=new MysqlTable("MyTableName"); $t->SetInfo("Data");

DoIt($t);

$Inf=$t->GetInfo(); // в $Inf будет строка Data!

Впрочем, в приведенном только что фрагменте это недоразумение можно легко преодолеть, передав функции ссылку на объект:

function DoIt(&$t)

{ $t->SetInfo("This is the new info!");

}

Я намеренно привел здесь пример, когда ограничение на копирование объектов все же можно обойти относительно безболезненно. Настало время описать неразрешимую (во всяком случае, похожим методом) задачу. Но прежде обратите внимание, что в нашем примере объект передается "вглубь" кода (внутрь функции), а не "наружу" (из функции). Вот как раз в последнем случае и будет возникать неразрешимая проблема.

480

Часть V. Приемы программирования на PHP

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

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

Возможно, вы спросите: зачем нам вообще такая функция, когда можно вос- пользоваться оператором new напрямую? Тогда еще раз перечитайте предпо- следнюю фразу предыдущего абзаца: "В случае, если таблица уже существует, новый объект не создается". В то же время оператор new всегда создает но- вый объект, что нам, конечно, не подходит. Ведь мы договорились никогда не иметь в программе двух разных объектов, связанных с одной и той же табли- цей.

Легко сказать — "возвращает уже существующий объект", но несколько сложнее — реализовать это. Рассмотрим два различных способа, с помощью которых мы можем достичь цели.

Как следует из законов Мэрфи, "у любой сложной задачи всегда имеется одно простое, красивое и легкое для пониманиянеправильное решение". В нашем случае это будет возврат из функции объекта класса MysqlTable "обычным" способом, подразумевающим копирование. Но ведь, по имеющейся между на- ми договоренности, объекты этого класса нельзя копировать!

Возврат ссылки на объект

Первый прием связан с новой возможностью PHP версии 4 — ссылочными переменными. Помните, в части III этой книги мы говорили, что функция может возвращать ссылку на переменную (объект), а не только копию переменной?.. В нашем случае это оказывается довольно удобно. Вот как могла бы выглядеть функция OpenTable() и использование для нее ссылок (листинг 31.4):