Определение класса
Наследование свойств: оператор extends
Конструкторы
Доступ к методам предков
Сериализация объектов
Ссылки в конструкторе
7.4.1. Доступ к перекрытым методам предков
Иногда возникает необходимость обратиться к функциям и переменным в классах-предках или вызвать, функции классов, для которых еще не созданы экземпляры. Для этой цели в РНР 4 предусмотрен специальный оператор — : :,. Вот пример
его использования:
class A {
function exampleO {
echo "Я функция A::example().<br>\n";
}
}
class В extends A {
function exampleO {
echo "А я переопределенная функция В::example().<br>\n";
A::example();
}
}
// Объект класса А не существует.
A::exampleO;
// Создаем объект класса В.
$b = new В;
$b->example();
В этом примере производится вызов функции example() класса А без создания
экземпляра класса, поэтому мы не можем использовать синтаксис вида $а - >
example(). Вместо этого мы вызываем example() как функцию класса, которая
принадлежит самому классу, а не какому-либо из его экземпляров. Результат выполнения этой программы приведен на рис. 7.2.
Рис. 7.2. РНР позволяет получить доступ к перекрытым методам без особых проблем
Следует иметь в виду, что в РНР могут существовать функции класса, но не могут
существовать переменные класса, как это принято в Java или C++, А поскольку
при отсутствии экземпляра класса переменные, объявленные в его определении,просто-напросто не существуют, функции класса не должны использовать ни одну
из них, хотя вполне могут пользоваться локальными и глобальными переменными.
Но вернемся к рассматриваемому нами примеру. В определении класса В производится перекрытие функции example (). Первоначальное определение функции затеняется и становится недоступным, если только вы Не укажете явным образом, что пытаетесь обратиться именно к реализации example() класса А. Для
этого используется запись A: :example(). (См. также использование синтаксиса
parent: :example() в следующем разделе.)
совет
В данном случае существует экземпляр класса, а следовательно существуют и переменные, описанные в определении класса. Поэтому в данном случае вы можете использовать как $this, так и переменные — члены класса.
7.4.2. Доступ к методам предков с помощью parent
В тех случаях, когда производный класс-потомок представляет собой специализированное уточнение базового класса, у вас может возникнуть необходимость использования во вновь разрабатываемых методах переменных, определенных в базовом классе.
Вместо того чтобы постоянно использовать полное имя базового класса, вы можете воспользоваться специальным идентификатором parent, который обозначает
имя родительского класса. Это заметно упрощает процесс разработки программ
и не приводит к возникновению конфликтов, если вы вдруг решите изменить иерархию классов в программе.
Вот как это выглядит на практике:
class А {
function exampleO {<br>
echo "Я главная функция А: : example().<br>\n";
}
}
class В extends A {
function exampleO {
echo "А я В:: example и помогаю работать функции"
" A: :example.<br>\n" ;
parent: :example() ;
}
}
$b = new В;
// Вызов В: : exampleO , которая вызовет A: :example() .
$b->example() ;
В седую старину, то есть в эпоху РНР 3, в процессе сериализации и последующего
восстановления экземпляры класса теряли связь с определениями классов, поэтому само понятие сериализации в РНР 3 было весьма условным. Но в РНР 4 сериализация реализована уже по-другому, что превращает ее в эффективный механизм сохранения объектов на диске.
7.5.1 . Функции serialize() и unserialize()
Основной рабочей лошадкой механизма сериализации является функция serialize(),
которая возвращает строку, содержащую побайтовое представление любого значения, которое может быть сохранено в РНР. Аналогичным образом функция
unserialize() может использовать эту строку для восстановления первоначальнoй структуры данных. Использование serialize применительно к экземпляру
класса приведет к сохранению всех переменных экземпляра. Конечно, функции
сохранены не будут, но в отличие от РНР 3 в формируемую строку будет также
помещено имя класса.
Чтобы иметь возможность восстановить экземпляр с помощью unserialize()
необходимо, чтобы на момент вызова этой функции был определен класс, к которому принадлежит восстанавливаемый экземпляр. То есть если в программе
stepl.php вы имели объект $а класса А и выполнили его сериализацию, вы получите строку, которая ссылается на класса А и содержит все значения переменных,
которые содержались в экземпляре $а. Если затем в программе step2.php мы попытаемся восстановить значение $а, то эта программа также должна иметь описание
класса А. Обычно в таких случаях определение классов помещается в отдельный
включаемый файл, который затем используется в рабочих программах. Вот пример, демонстрирующий использование сериализации:
<?
// Описание класса А - classa.inc:
class A {
var $one » 1;
function show_one() {
echo $this->one;
}
}
?>
<?
// Сохранение объекта - stepl.php:
1nclude("classa.1nc");
a = new A;
s = serialize(a) ;
// помещаем s там, где step2.php сможет его затем отыскать
// и куда может записать данные пользователь nobody (!)
fp » fopen("/tmp/store", "w");
fwrlte(fp.s);
echo s;
fclose(fp);
?>
<?
// Восстановление объекта - step2.php:
include("classa.inc");
s = implode("", @file("/tmp/store"));
a = unserialize(s);
// Применяем функцию-член show_one
a->show_one();
?>
Результаты выполнения программ stepl.php и step2.php приведены на рис. 7.3 и 7.4.
Рис. 7.3. Работа механизма сериализации: сохранение объекта во внешнем дисковом файле
Рис. 7.4. Работа механизма сериализации: восстановление объекта
Если вы используете средства сеансовой поддержки РНР и применяете функцию
session_register() для регистрации объектов в программе, эти объекты автоматически подвергаются сериализации в конце выполнения каждой РНР-страницы
и так же автоматически восстанавливаются в начале любой последующей РНР-страницы. Иначе говоря, как только объекты превратились в часть вашего сеанса,
они автоматически поддерживаются в активном состоянии на всем его протяжении.
Понятно, что при таком подходе вы обязательно должны включать в каждую страницу определения всех используемых классов, даже если вы не планируете в какой-то из страниц пользоваться тем или иным экземпляром. В противном случае
вы получите сообщение об ошибке в процессе восстановления экземпляра класса
и выполнение программы РНР будет прервано.
примечание
Итак, если в рассмотренном выше примере $а объявляется как часть сеанса с помощью
вызова session_register("a"), вы должны включать файл classa.inc во все страницы, а не
только в stepl .php и step2.php.
7.5.2. Специальные функции_sleep и_wakeup
Функция serialize () автоматически проверяет, не содержит ли определение
сохраняемого класса функцию с зарезервированным именем_sleep. В случае
обнаружения такой функции она автоматически запускается на выполнение перед началом сериализацш. К числу выполняемых ею задач обычно относится очистка отдельных полей объекта, и предполагается, что в результате ее работы будет
возвращен массив, в котором содержатся имена всех переменных, подлежащих сериализации.
примечание
Чаще всего_sleep используется для закрытия соединений с серверами баз данных, которые могут выполняться внутри экземпляра класса, подтверждения транзакций, сноса
файловых буферов на диск и т. д. Кроме того, функция используется в тех случаях, когда
вам необходимо сохранить только часть огромного объекта.
Аналогичным образом функция unserialiize() проверяет наличие в определений класса функции_wakeup. При ее обнаружении производится автоматическая реконструкция любых ресурсов, к которым должен быть подключен восстанавливаемый объект.
примечание
Функция-wakeup используется в основном для восстановления соединений с серверами баз данных, которые неизбежно разрываются при сериализации, а также для открытия внешних дисковых файлов и других подобных задач.
Использование механизма ссылок (см. раздел 4.3) внутри конструкторов может
привести к неожиданным результатам. В этом разделе мы рассмотрим, как избежать различных неприятностей.
Перед нами пример определения класса:
class foo {
function foo($name) {
// ссылка внутри глобального массива $globalref
global $globalref;
$globalref[] = &$this;
// устанавливаем имя равным переданному значению
$th1s->setName($name);
// и выводим его на печать
$th1s->echoName();
}
function echoName() {
echo "<br>",$th1s->Name;
}
function setName($name) {
$th1s->Name = $name;
}
}
Теперь давайте взглянем, нет ли разницы между экземпляром $ bаr1, который создается с помощью оператора копирования =, и $bаr2, созданным с помощью ссылочного оператора =&. Для этого мы воспользуемся следующим фрагментом:
$barl = new foo('Установлено в конструкторе');
$barl->echoName():
$globalref[0]->echoName();
/* Результат:
Установлено в конструкторе
Установлено в конструкторе
Установлено в конструкторе */
$bаr2 =& new foo( 'Установлено в конструкторе');
$bar2->echoName();
$globalref[l]->echoName();
/* Результат:
Установлено в конструкторе
Установлено в конструкторе
Установлено в конструкторе */
На первый взгляд никакой разницы не видно, но на самом деле различия есть,
и весьма существенные! Дело в том, что $bаr 1 и $globalref [0] не являются ссылками на одну и ту же структуру данных — это разные переменные. Такое поведение обусловлено тем, что оператор new по умолчанию создает копию объекта, а не простую ссылку на него.
Никакой потери производительности из-за копирования не происходит, поскольку в РНР 4 используется механизм контроля количества ссылок, аналогичный
используемому вPerl и Лиспе. Кроме того, работать с копиями зачастую оказывается предпочтительнее, поскольку как раз создание ссылок требует больше времени, чем простое копирование.
Взгляните на следующий пример как подтверждение вышеизложенного:
// Как вы думаете, что произойдет, если мы изменим имя?<br>
// Вы рассчитываете, что и $bar, и $globalref [0]
// изменят свои имена?
$barl->setName( 'Внешняя установка'};
// Как же! Это не тот случай...
$barl->echoName() ;
$globalref [0]->echoName():
/* Результат:
Установлено в конструкторе
Внешняя установка */
// А что произойдет в случае $Ьаr2 и $globalref [1]
$bar2->setName( 'Внешняя установка') ;
// поскольку $bar2->Name и $globalref [1] ->Name
// являются фактически одной и той же переменной
$bar2->echoName() ;
$globalref[l]->echoName();
/* Результат:
Внешняя установка
Внешняя установка */
И в заключение еще один пример, который оставляю читателю в качестве домашнего задания:
class a {
function a($1) {
$th1s->value * $i ;
// подумайте, почему нам не надо использовать
// здесь ссылку?
$th1s->b = new b($th1s);
}
function createRef() {
$th1s->c = new b($th1s);
}
function echoValue() {
echo " ", "class ".get class($th1s) , ": ",$th1s->value;
}
class b {
function b(&$a) {
$th1s->a - &$a;
function echoValue() {
echo "<br>","class ".get class($th1s) . ": ",$th1s->a->value;
// Подумайте, почему использование простой операции копирования в
// строке, отмеченной комментарием *, приводит к. неверному результату
$a =& new a(10) ;
$a->createRef () ;
$a->echoValue();
$a->b->echoValue(:)
$a->c->echoValue():
$a->value = 11;
$a->echoValue();,
$a->b->echoValue(); // *
$a->c->echoValue();
Результат :
class a: 10
class b: 10
class b: 10
class a: 11
class b: 11
class b: 11
*/
|