Классы и объекты

Начнем с основных понятий объектно-ориентированного программирования – класса и объекта. Существует множество определений этих понятий. Мы дадим следующее: объект – это структурированная переменная, содержащая всю информацию о некотором физическом предмете или реализуемом в программе понятии, класс – это описание таких объектов и действий, которые можно с ними выполнять.
В PHP класс определяется с помощью следующего синтаксиса:

class Имя_класса{
var $имя_свойства;
/*список свойств*/
function имя_метода( ){
/* определение метода */
}
/*список методов*/
}

Имена свойств объектов класса объявляются с помощью ключевого слова var, методы, применимые к объектам данного класса, описываются функциями. Внутри определения класса можно использовать ключевое слово this для обращения к текущему представителю класса.
Например, нам нужно создать класс, описывающий категорию статей. У каждой статьи имеются такие свойства, как название, автор и краткое содержание. Какие действия мы хотим совершать со статьями? Возможно, нам понадобится задавать значения перечисленным свойствами статьи, отображать статью в браузере. Тогда определение этого класса может выглядеть следующим образом:

<?
class Articles { // Создаем класс Статей
var $title;
var $author;
var $description;
// метод, который присваивает значения
// атрибутам класса
function make_article($t, $a, $d){
$this->title = $t;
$this->author = $a;
$this->description = $d;
}
//метод для отображения экземпляров класса
function show_article(){
$art = $this->title . "<br>" .
$this->description .
"<br>Автор: " . $this->author;
echo $art;
}
}
?>

Итак, для описания физических объектов типа «статья» мы создали класс с именем Articles, состоящий из трех переменных, содержащих характеристики статьи, и двух функций для создания конкретной статьи и для ее отображения.
Как известно, работая с PHP, можно периодически переключаться в режим HTML. В этом случае программа состоит из нескольких кусков (блоков) кода. Определение класса нельзя разносить по разным блокам php-кода и тем более по разным файлам. То есть если написать:

<?phpclass Articles { // Начало описания класса
var $title;
?>
<?php
// продолжение описания класса
function show_article(){
// содержание метода
}
} // конец описания класса
?>

то программа не будет работать корректно.
Несколько замечаний по поводу имен классов. Имя класса должно удовлетворять правилам именования объектов в языке PHP, но есть ряд имен, которые зарезервированы разработчиками для своих целей. В первую очередь это имена, начинающиеся с символа подчеркивания «_». Для создания классов и функций нельзя использовать такие имена. Кроме того, зарезервировано имя stdClass, поскольку оно используется внутри движка PHP.

Инициализация переменных

Часто некоторым атрибутам класса бывает необходимо присваивать значения сразу после создания представителя класса. Когда мы создавали класс статей, для присваивания значений атрибутам (свойствам) класса мы использовали специальную функцию make_article(). Вообще говоря, мы поступили не совсем верно, потому что занялись изобретением велосипеда. Специально для задания начальных значений атрибутам класса существует два стандартных метода. В PHP4 можно инициализировать значения с помощью оператора var или с помощью функции конструктора. С помощью var можно инициализировать только константные значения. Для задания не константных значений используют функцию конструктор, которая вызывается автоматически, когда объект конструируется из класса. Функция-конструктор должна иметь имя, совпадающее с именем всего класса, в котором она определена.
Приведем пример. Допустим, при создании объекта «статья» мы хотим установить его свойства следующим образом: автора – равным строке «Иванов», название и краткое содержание – соответствующим элементам глобального массива $_POST, а дату публикации статьи – текущей дате. Тогда следующее описание класса не является корректным в PHP4:

<?
class Articles { // Создаем класс Статей
var $title= $_POST["title"];
var $author = "Иванов";
var $description = $_POST["description"];
var $published = date("Y-m-d");
// метод, который присваивает значения
// атрибутам класса
}
?>

А вот такое описание класса в PHP4 будет работать так, как нужно:

<?
class Articles { // Создаем класс Статей
var $title;
var $author = "Иванов";
var $description;
var $published;
// метод, который присваивает значения
// атрибутам класса
function Articles(){
$this->title = $_POST["title"];
$this->description = $_POST["description"];
$this ->published = date("Y-m-d");
}
}
?>

Отметим, что в PHP3 и PHP4 конструкторы работают по-разному. В PHP3 функция становилась конструктором, если она имела то же имя, что и класс, а в PHP4 – если она имеет то же имя, что и класс, в котором она определена. Разница в подходах видна, когда один класс расширяет другой и происходит наследование свойств и методов базового класса. Но об этом мы поговорим чуть позже. В PHP5 конструктор класса именуется _construct. Кроме того, в PHP5 появились и деструкторы – функции, которые вызываются автоматически перед уничтожением объекта. В PHP5 функция-деструктор должна быть названа _destruct

Объекты

В одной из первых лекций мы упоминали о существовании в PHP такого типа данных, как объект. Класс – это описание данных одного типа, данных типа объект. Классы являются как бы шаблонами для реальных переменных. Переменная нужного типа создается из класса с помощью оператора new. Создав объект, мы можем применять к нему все методы и получать все свойства, определенные в описании класса. Для этого используют такой синтаксис: $имя_объекта->название_свойства или $имя_объекта->название_метода(список аргументов). Заметим, что перед названием свойства или метода знака $ не ставят.

Пример 1. Доступ к методам и свойствам объекта

<?php$art = new Articles; 
// создаем объект $artecho ($art ->title);
// выводим название объекта $art$another_art = new Articles;
// создаем объект $another_art$another_art->show_article();
// вызываем метод для
// отображения объекта в браузер
?>

Каждый из объектов класса имеет одни и те же свойства и методы. Так, у объекта $art и у объекта $another_art есть свойства title, description, author и методы Articles(), show_article().

Но это два разных объекта. Представим себе объект как директорию в файловой системе, а его характеристики – как файлы в этой директории. Очевидно, что в каждой директории могут лежать одинаковые файлы, но тем не менее они считаются различными, поскольку хранятся в разных директориях. Точно так же свойства и методы считаются различными, если они применяются к разным объектам. Чтобы получить нужный файл из директории верхнего уровня, мы пишем полный путь к этому файлу. При работе с классами нужно указывать полное имя функции, которую мы хотим вызвать. Директорией верхнего уровня в PHP будет пространство глобальных переменных, а путь указывается с помощью разделителя ->. Таким образом, имена $art->title и $another_art->title обозначают две разные переменные. Переменная в PHP имеет только один знак доллара перед именем, поэтому нельзя писать $art->$title. Эта конструкция будет рассмотрена не как обращение к свойству title объекта $art, а как обращение к свойству, имя которого задано переменной $title (например, $art->"").

Пример 2. Установка значений свойств

<?php$art->title = "Введение в Internet"; 
// так можно установить
// значение свойства объекта$art->$title = "Введение в Internet";
// так нельзя установить
// значение свойства объекта$property = "title";
$art->$property = "Введение в Internet";
// так можно установить значение
// свойства объекта
?>

Создавая класс, мы не можем знать, какое имя будет иметь объект этого класса, тем более что объектов может быть много и все могут иметь разные имена. Соответственно мы не знаем, как обращаться к объекту внутри определения класса. Для того чтобы иметь доступ к функциям и переменным внутри определения класса, нужно использовать псевдопеременную $this. Например, $this->title возвращает значение свойства title у текущего объекта данного класса. Иногда эту переменную предлагают читать как «мое собственное» (к примеру, по отношению к свойству).

Наследование

extends
Механизм наследования – очень важная часть всего объектно-ориентированного подхода. Попытаемся объяснить его суть на примере. Допустим, мы создаем описание человека. Очевидно, что сделать это мы можем по-разному, в зависимости от того, для чего нужно это описание. Можно описать человека как программиста: он знает такие-то языки программирования, операционные системы, участвовал в стольких-то проектах. Однако если человек программист, то он не перестает быть человеком вообще, т.е. он имеет имя, фамилию, место жительства и т.п. Если перевести наши рассуждения в термины объектно-ориентированного программирования, то можно сказать, что мы описали два класса – класс людей и класс программистов, каждый со своими свойствами и методами. Причем класс программистов, очевидно, обладает всеми свойствами класса людей и при этом имеет свои специфические характеристики, т.е. класс программистов является подклассом класса людей.
Так, если у человека вообще есть имя, то у программиста оно тоже должно быть, но не наоборот. Кроме программистов можно выделить еще множество классов по профессиональной принадлежности людей. И все они будут подклассами класса людей. Часто на практике удобно определять общий класс, который может использоваться сразу в нескольких проектах (например, класс людей или личностей), и адаптировать его для специфических нужд каждого проекта (например, как класс программистов). Как это можно реализовать? С помощью механизма расширений. Любой класс может быть расширением другого класса. Расширяющий (или производный) класс, кроме тех свойств и методов, которые описаны в его определении, имеет все функции и свойства основного (базового класса).

В нашем примере класс программистов – расширяющий, а класс всех людей – базовый. Из класса нельзя удалить никакие существующие свойства и функции, класс можно только расширить. Расширяющий класс в PHP4 всегда зависит только от одного базового класса, поскольку множественное наследование в PHP не поддерживается. Расширяются классы в PHP с помощью ключевого слова extends.

Пример 1. Использование механизма наследования

<?phpclass Person { // определяем класс Личности
var $first_name; // имя личности
var $last_name; // фамилия личности
function make_person($t,$a){
// метод устанавливает
// значения имени и фамилии объекта
$this->first_name = $t;
$this->last_name = $a;
}
function show_person(){
// метод отображает информацию о личности
echo ("<h2>" . $this->first_name . " " .
$this->last_name . "</h2>");
}
}
class Programmer extends Person{
// определяем класс
// Programmer, расширяющий Person
var $langs = array ("Lisp");
// константным массивом
// задать переменную в var можно
function set_lang($new_lang){
// метод добавляет еще
// один язык к списку известных
$this->langs[] = $new_lang;
}
}
?>

Класс Programmer имеет те же переменные и функции, что и класс Person, плюс переменную $langs, в которой содержится список изученных программистом языков, и функцию set_lang для добавления еще одного языка к списку изученных. Создать представителя класса программистов можно обычным способом с помощью конструкции new. После этого можно устанавливать и получать список языков, которые знает программист, и в то же время можно использовать функции, заданные для класса Person, т.е. устанавливать и получать имя и фамилию программиста и отображать сведения о нем в браузере:

<?php$progr = new Programmer;
$progr -> set_lang("PHP");
// методы, определенные для
// класса Programmerprint_r ($progr->langs);
// методы, определенные для класса Person$progr->make_person("Bill","Gates");
$progr->show_person();
?>

Отношения, в которых состоят созданные нами классы Person и Programmer, называют также отношениями родитель–потомок. Класс Person – родитель, а его потомки, такие как класс Programmer, создаются, основываясь на нем, с помощью расширений. Любой класс может стать родительским и соответственно породить потомков.
Порядок определения классов имеет значение. Нельзя сначала определить класс Programmer, расширяющий класс Person, а уже потом сам класс Person. Класс должен быть определен перед тем, как он будет использоваться (расширяться).

extends

Механизм наследования – очень важная часть всего объектно-ориентированного подхода. Попытаемся объяснить его суть на примере. Допустим, мы создаем описание человека. Очевидно, что сделать это мы можем по-разному, в зависимости от того, для чего нужно это описание. Можно описать человека как программиста: он знает такие-то языки программирования, операционные системы, участвовал в стольких-то проектах. Однако если человек программист, то он не перестает быть человеком вообще, т.е. он имеет имя, фамилию, место жительства и т.п. Если перевести наши рассуждения в термины объектно-ориентированного программирования, то можно сказать, что мы описали два класса – класс людей и класс программистов, каждый со своими свойствами и методами. Причем класс программистов, очевидно, обладает всеми свойствами класса людей и при этом имеет свои специфические характеристики, т.е. класс программистов является подклассом класса людей.
Так, если у человека вообще есть имя, то у программиста оно тоже должно быть, но не наоборот. Кроме программистов можно выделить еще множество классов по профессиональной принадлежности людей. И все они будут подклассами класса людей. Часто на практике удобно определять общий класс, который может использоваться сразу в нескольких проектах (например, класс людей или личностей), и адаптировать его для специфических нужд каждого проекта (например, как класс программистов). Как это можно реализовать? С помощью механизма расширений. Любой класс может быть расширением другого класса. Расширяющий (или производный) класс, кроме тех свойств и методов, которые описаны в его определении, имеет все функции и свойства основного (базового класса).

В нашем примере класс программистов – расширяющий, а класс всех людей – базовый. Из класса нельзя удалить никакие существующие свойства и функции, класс можно только расширить. Расширяющий класс в PHP4 всегда зависит только от одного базового класса, поскольку множественное наследование в PHP не поддерживается. Расширяются классы в PHP с помощью ключевого слова extends.

Пример 1. Использование механизма наследования

<?phpclass Person { // определяем класс Личности
var $first_name; // имя личности
var $last_name; // фамилия личности
function make_person($t,$a){
// метод устанавливает
// значения имени и фамилии объекта
$this->first_name = $t;
$this->last_name = $a;
}
function show_person(){
// метод отображает информацию о личности
echo ("<h2>" . $this->first_name . " " .
$this->last_name . "</h2>");
}
}
class Programmer extends Person{
// определяем класс
// Programmer, расширяющий Person
var $langs = array ("Lisp");
// константным массивом
// задать переменную в var можно
function set_lang($new_lang){
// метод добавляет еще
// один язык к списку известных
$this->langs[] = $new_lang;
}
}
?>

Класс Programmer имеет те же переменные и функции, что и класс Person, плюс переменную $langs, в которой содержится список изученных программистом языков, и функцию set_lang для добавления еще одного языка к списку изученных. Создать представителя класса программистов можно обычным способом с помощью конструкции new. После этого можно устанавливать и получать список языков, которые знает программист, и в то же время можно использовать функции, заданные для класса Person, т.е. устанавливать и получать имя и фамилию программиста и отображать сведения о нем в браузере:

<?php$progr = new Programmer;
$progr -> set_lang("PHP");
// методы, определенные для
// класса Programmerprint_r ($progr->langs);
// методы, определенные для класса Person$progr->make_person("Bill","Gates");
$progr->show_person();
?>

Отношения, в которых состоят созданные нами классы Person и Programmer, называют также отношениями родитель–потомок. Класс Person – родитель, а его потомки, такие как класс Programmer, создаются, основываясь на нем, с помощью расширений. Любой класс может стать родительским и соответственно породить потомков.
Порядок определения классов имеет значение. Нельзя сначала определить класс Programmer, расширяющий класс Person, а уже потом сам класс Person. Класс должен быть определен перед тем, как он будет использоваться (расширяться).

Конструкторы

Теперь, после знакомства с механизмом наследования в PHP, мы можем прокомментировать различие между конструкторами PHP4 и PHP3 и более подробно рассказать о конструкторах вообще. Напомним, что в PHP3 конструктор – это функция, имя которой совпадает с именем класса. А в PHP4 – функция, имя которой совпадает с именем класса, в котором она определена.

Пример 1. Использование конструктора

<?phpclass Programmer extends Person{ 
// определяем класс
// Programmer, расширяющий Person
var $langs = array ("Lisp");
function Programmer(){
// этот конструктор будет
// работать и в PHP3, и в PHP4
$this->make_person("Иван","Петров");
}
}
?>

Здесь функция Programmer() является конструктором, т.е. выполняется сразу после создания любого представителя класса Programmer, задавая ему имя «Иван» и фамилию «Петров». Конструкторы, как и любые другие функции, могут иметь аргументы. В этом случае, создавая представителя класса, нужно указать значения этих параметров. Аргументы конструктора могут иметь и значения по умолчанию. Если все аргументы имеют значения по умолчанию, тогда можно создавать экземпляр класса без параметров.

Пример 2. Использование конструктора

<?phpclass Programmer extends Person{ 
// определяем класс
// Programmer, расширяющий Person
var $langs = array ("Lisp");
function Programmer($n = "Иван",
$f = "Петров"){
// это конструктор
$this->make_person($n,$f);
}
}
$default_progr = new Programmer();
// создаст программиста Ивана Петрова$new_progr = new Programmer("Вася",
"Сидоров");
// создаст программиста Васю Сидороваprint_r($new_progr);
/* выведет информацию о переменной $new_progr, т.е. свойства объекта и их значения */
?>

Приведенные примеры будут работать и в PHP3, и в PHP4, конечно если дописать в них определение базового класса Person. Допустим, ситуация немного другая: конструктор имеется только у базового класса Person:

<?phpclass Person { // определяем класс Личности
var $first_name;
var $last_name;
function Person($t,$a){ // конструктор
$this->first_name = $t;
$this->last_name = $a;
}
/* ... */
}
class Programmer extends Person{
// определяем класс
// Programmer, расширяющий Person
var $langs = array ("Lisp");
function set_lang($new_lang){
$this->langs[] = $new_lang;
}
}
$new_progr = new Programmer("Вася",
"Сидоров");
?>

Что произойдет в этом случае при создании объекта класса Programmer, будет ли автоматически вызвана какая-либо функция? В PHP3 ничего не произойдет, поскольку в этом классе нет функции с именем Programmer() (здесь конструктор – это функция, имя которой совпадает с именем класса). В PHP4 будет вызван конструктор базового класса, если он существует, т.е. вызовется функция Person() из класса Person (здесь конструктор – функция, имя которой совпадает с именем класса, в котором она определена).

Еще одна ситуация – в базовом классе есть функция, имя которой совпадает с именем расширяющего класса, а в расширяющем классе нет конструктора.

<?phpclass Person { // определяем класс Личности
var $first_name;
var $last_name;
function Person($t,$a){ // конструктор
$this->first_name = $t;
$this->last_name = $a;
}
function Programmer($new_lang){
echo "Я – программист";
}
}
class Programmer extends Person{
// определяем класс
// Programmer, расширяющий Person
var $langs = array ("Lisp");
function set_lang($new_lang){
$this->langs[] = $new_lang;
}
}
$new_progr = new Programmer("Вася",
"Сидоров");
?>

В этом случае PHP3 вызовет в качестве конструктора функцию Programmer() из описания класса Person. Поскольку конструктор – это функция, у которой то же имя, что и у класса. И неважно, определена ли эта функция в самом классе или она наследуется из базового класса. В PHP4 класс Programmer не будет иметь своего конструктора, поэтому вызовется конструктор базового класса.
Ни в PHP 3, ни в PHP 4 конструктор базового класса не вызывается автоматически из конструктора порожденного класса.

Оператор ::

Иногда внутри описания класса возникает необходимость сослаться на функции или переменные из базового класса. Бывает, что нужно ссылаться на функции в классе, ни один представитель которого еще не создан. Как быть в таком случае? В PHP4 для этого существует специальный оператор «::»

Например, вот так можно вызвать в описании класса Programmer функцию show_name() из базового класса Person и функцию say_hello(), заданную в описании класса Programmer, когда ни один объект этого класса еще не был создан:

<?phpclass Person { // определяем класс Личности
var $first_name;
var $last_name;
function Person($t,$a){ // конструктор
$this->first_name = $t;
$this->last_name = $a;
}
function show_name(){
// метод отображает информацию о личности
echo ("Меня зовут, " .
$this->first_name . " " .
$this->last_name . "!<br>");
}
}
class Programmer extends Person{
// определяем класс
// Programmer, расширяющий Person
function set_lang($new_lang){
// метод добавляет еще
// один язык к списку известных
$this->langs[] = $new_lang;
Person::show_name();
// вызываем функцию из базового класса
echo "И я знаю теперь еще и " .
$new_lang;
}
function show_name(){
echo ("Я программист, " .
$this->first_name . " " .
$this->last_name . "!<br>");
}
function say_hello(){
echo "Привет!<br>";
}
}
Programmer::say_hello();
// вызываем функцию, когда ни
// один объект ее класса еще не создан$new_progr = new Programmer("Вася","Сидоров");
$new_progr->set_lang("PHP");
?>

В результате работы этой программы получим следующее:

Привет!
Меня зовут Вася Сидоров!
И я знаю теперь еще и PHP

С помощью команды Programmer::say_hello(); мы вызываем функцию say_hello класса Programmer как таковую, а не как метод, применяемый к объекту данного класса. В этот момент переменных класса нет. Поэтому функции, вызываемые до создания объекта, не могут пользоваться переменными класса и конструкцией this, но могут пользоваться локальными и глобальными переменными.
В определении класса Programmer мы переопределили функцию show_name(), поэтому вызвать функцию show_name() из базового класса Person можно только с помощью оператора «::» Вообще говоря, внутри определения класса мы можем вызывать любые методы и свойства, заданные в его базовом классе с помощью обычного $this, если только порожденный класс не переопределяет эти свойства и методы, как в нашем примере.

Оператор parent

В приведенном выше примере, обращаясь к базовому классу, мы использовали его имя (мы писали Person::show_name()).

Это не совсем удобно, потому что имя класса или иерархия классов может измениться, и тогда придется переписывать код описаний всех классов с тем, чтобы привести используемые в них имена в соответствие с новой иерархией. Чтобы избежать подобной ситуации, вместо имени базового класса нужно использовать ключевое слово parent (например, parent::show_name()). Parent ссылается на класс, прописанный после extends в объявлении вашего класса.Поэтому если вдруг иерархия классов изменится, то достаточно будет внести изменения в имена, указанные после extends в описаниях классов

Объектная модель PHP5

Кроме нового названия для конструкторов и появления деструкторов в PHP5 произошло еще достаточно много изменений. Мы не будем обсуждать их подробно, только опишем в общих чертах. Основное изменение – это передача значений параметров класса по ссылке и присвоение объектов по ссылке, а не по значению, как это было в PHP4.

В PHP5 если создаются две равные переменные типа объект, то они указывают на одно значение и изменяются одновременно (мы приводили похожий пример с переменными строкового типа).

В связи с этим появился новый механизм для создания копий объектов – так называемое клонирование. В PHP4 все методы и переменные класса доступны извне, т.е. они всегда являются открытыми. В PHP5 переменные и методы можно делать открытыми (доступными отовсюду), закрытыми (доступными только внутри класса) и защищенными (доступными внутри класса и в его производных классах). Кроме того, появилась возможность создавать интерфейсы и абстрактные классы и многое другое. В целом объектная модель в PHP5 значительно усовершенствована для более точного соответствия объектно-ориентированной парадигме программирования.

Решение задачи

Итак, мы хотели по выбору пользователя генерировать форму для ввода описания статьи или человека и отображать данные, введенные в эту форму. Попробуем решить эту задачу, используя объектно-ориентированный подход. Для начала создадим форму, где пользователь выбирает, что он хочет создать, – описание статьи или человека (точнее, это будут две формы):

<form action="task1.php">
Создать описание статьи: <input type=submit
name=art_create
value="Create Article">
</form>
<form action="task1.php">
Создать описание личности: <input
type=submit name=pers_create
value="Create Person">
</form>

Теперь напишем файл для обработки этих форм. В нем создадим два класса – статьи и личности. У каждого класса имеется метод для инициализации его переменных и метод для отображения объектов данного класса. При решении задачи будут использованы две функции, встроенные в PHP для работы с классами и объектами. Это функция get_class(объект), возвращающая имя класса, экземпляром которого является объект, переданный ей в качестве параметра. И функция get_class_vars(имя класса), которая возвращает массив всех свойств класса и их значений по умолчанию. Аналогично можно получить массив имен всех методов класса: get_class_methods (имя класса)

Class

Каждое определение класса начинается ключевым словом class, следующим за именем класса, которое может быть любым не зарезервированным в PHP именем, за которым следует пара фигурных скобок, внутри которых содержится определение членов класса и методов. Псевдо-переменная $this доступна, когда метод вызывается внутри контекста объекта. $this - это ссылка на вызывающий объект (обычно это объект, которому принадлежит метод, но который может быть другим объектом, если метод вызывается статически из контекста другого объекта). Это иллюстрируется следующим примером:

Переменная $this в объектно-ориентированном языке

<?php
class A
{
function foo()
{
if (isset($this)) {
echo '$this определена (';
echo get_class($this);
echo ")\n";
} else {
echo "\$this не определена.\n";
}
}
}

class B
{
function bar()
{
A::foo();
}
}

$a = new A();
$a->foo();
A::foo();
$b = new B();
$b->bar();
B::bar();
?>

Результат выполнения данного примера:

$this определена (a)
$this не определена.$this определена (b)
$this не определена.

Простое определение класса

<?php
class SimpleClass
{
// member declaration
public $var = 'a default value';

// method declaration
public function displayVar() {
echo $this->var;
}
}
?>

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

Дефолтное значение члена класса

<?php
class SimpleClass
{
// неправильная декларация члена:
public $var1 = 'hello '.'world';
public $var2 = <<hello worldEOD;
public $var3 = 1+2;
public $var4 = self::myStaticMethod();
public $var5 = $myVar;

// правильное объявление члена
public $var6 = myConstant;
public $var7 = self::classConstant;
public $var8 = array(true, false);


}
?>

Абстрактные классы

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

Пример абстрактного класса

<?php
abstract class AbstractClass {

/* Данный метод должен быть определён в дочернем классе */
abstract protected function getValue();

/* Общий метод */
public function print() {
print $this->getValue();
}

}

class ConcreteClass1 extends AbstractClass {

protected function getValue() {
return "ConcreteClass1";
}

}

class ConcreteClass2 extends AbstractClass {

protected function getValue() {
return "ConcreteClass2";
}

}

$class1 = new ConcreteClass1;
$class1->print();

$class2 = new ConcreteClass2;
$class2->print();
?>

Код, предназначенный для прежних версий PHP, должен работать без изменений, если в нём отсутствуют классы или функции, именованные "abstract".

Интерфейсы объектов

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

Интерфейсы объявляются так же, как и обычные классы, но с использованием ключевого слова "interface"; тела методов интерфейсов должны быть пустыми. Для включения интерфейса в класс программист должен использовать ключевое слово "implements" и описать функционал методов, перечисленных во включаемом интерфейсе. Если это требуется, классы могут включать более одного интерфейса путём их перечисления через пробел.

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

Пример интерфейса

<?php
interface ITemplate
{
public function setVariable($name, $var);
public function getHtml($template);
}

class Template implements ITemplate
{
private $vars = array();

public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}

public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{'.$name.'}', $value, $template);
}

return $template;
}
}
?>

Перегрузка

Вызовы методов, как и обращения к свойствам объекта, могут быть перегружены с использованием методов __call, __get и __set. Эти методы будут срабатывать только в том случае, если объект или наследуемый объект не содержат свойства или метода, к которому осуществляется доступ.

Перегрузка свойств

void __set ( string имя, mixed значение )
void __get ( mixed имя )

С помощью этих методов обращения к свойствам класса могут быть перегружены с целью выполнения произвольного кода, описанного в классе. В аргументе имя передаётся имя свойства, к которому производится обращение. Аргумент значение метода __set() должен содержать значение, которое будет присвоено свойству класса с именем имя.

Пример перегрузки с использование __get и __set

<?php
class Setter {
public $n;
private $x = array("a" => 1, "b" => 2, "c" => 3);

function __get($nm) {
print "ЧИтаем [$nm]\n";

if (isset($this->x[$nm])) {
$r = $this->x[$nm];
print "Получили: $r\n";
return $r;
} else {
print "Ничего!\n";
}
}

function __set($nm, $val) {
print "Пишем $val в [$nm]\n";

if (isset($this->x[$nm])) {
$this->x[$nm] = $val;
print "OK!\n";
} else {
print "Всё плохо!\n";
}
}
}

$foo = new Setter();
$foo->n = 1;
$foo->a = 100;
$foo->a++;
$foo->z++;
var_dump($foo);
?>

Результатом выполнения будет:

Пишем 100 в [a]
OK!
Читаем [a]
Получили: 100
Пишем 101 в [a]
OK!
Читаем [z]
Ничего!
Пишем 1 в [z]
Всё плохо!
object(Setter)#1 (2) {
["n"]=>
int(1)
["x:private"]=>
array(3) {
["a"]=>
int(101)
["b"]=>
int(2)
["c"]=>
int(3)
}
}

Перегрузка методов

mixed __call ( string имя, array аргументы )

С использованием этого метода, методы класса могут быть перегружены с целью выполнения произвольного кода, описанного в классе. В аргументе имя передаётся имя вызванного метода. Аргументы, которые были переданы методу при обращении, будут возвращены чере аргументы. Значение, возвращённое методом __call(), будет передано вызывающему оператору.

Пример перегрузки с использованием __call

<?php
class Caller {
private $x = array(1, 2, 3);

function __call($m, $a) {
print "Вызван метод $m :\n";
var_dump($a);
return $this->x;
}
}

$foo = new Caller();
$a = $foo->test(1, "2", 3.4, true);
var_dump($a);
?>

Результатом выполнения будет:

Вызван метод test:array(4) {
[0]=>
int(1)
[1]=>
string(1) "2"
[2]=>
float(3.4)
[3]=>
bool(true)
}
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}

Итераторы объектов

PHP 5 предоставляет механизм итераторов для получения списка всех свойств какого-либо объекта, например, для использования совместно с оператором foreach. По умолчанию, в итерации будут участвовать все свойства, объявленные как public.

Итерация простого объекта

<?php
class MyClass {
public $var1 = 'value 1';
public $var2 = 'value 2';
public $var3 = 'value 3';

protected $protected = 'protected';
private $private = 'private';

}

$class = new MyClass();

foreach($class as $key => $value) {
print "$key => $value\n";
}

Результат:

var1 => value 1
var2 => value 2
var3 => value 3

Как показывает результат, foreach проитерировал все принадлежащие объекту public-свойства. Кроме того, программист может включить (implement) в свой класс один из внутренних объектов PHP5, именуемый Iterator. Это позволит программисту самому определить, каким именно образом будет осуществляться итерация объекта.

Объект Iteration, включающий интерфейс Iterator

<?php
class MyIterator implements Iterator {

private $var = array();

public function __construct($array) {
if (is_array($array) ) {
$this->var = $array;
}
}

public function rewind() {
echo "перемотка в начало\n";
reset($this->var);
}

public function current() {
$var = current($this->var);
echo "текущий: $var\n";
return $var;
}

public function key() {
$var = key($this->var);
echo "ключ: $var\n";
return $var;
}

public function next() {
$var = next($this->var);
echo "следующий: $var\n";
return $var;
}

public function valid() {
$var = $this->current() !== false;
echo "верный: {$var}\n";
return $var;
}

}

$values = array(1,2,3);
$it = new MyIterator($values);

foreach ($it as $a => $b) {
print "$a: $b\n";
}

Результатом выполнения этого кода станет:

перемотка в началотекущий: 1
верный: 1
текущий: 1
ключ: 0
0: 1
следующий: 2
текущий: 2
верный: 1
текущий: 2
ключ: 1
1: 2
следующий: 3
текущий: 3
верный: 1
текущий: 3
ключ: 2
2: 3
следующий:текущий:верный:

Программист также может объявить класс так, чтобы ему не пришлось описывать все методы, перечисленные в интерфейсе Iterator, включая интерфейс PHP5 IteratorAggregate.

Объект Iteration, включающий интерфейс IteratorAggregate

<?php
class MyCollection implements IteratorAggregate {
private $items = array();
private $count = 0;

/* Required definition of interface IteratorAggregate */
public function getIterator() {
return new MyIterator($this->items);
}

public function add($value) {
$this->items[$this->count++] = $value;
}

}

$coll = new MyCollection();
$coll->add('value 1');
$coll->add('value 2');
$coll->add('value 3');

foreach ($coll as $key => $val) {
echo "key/value: [$key -> $val]\n\n";
}

?>

Результат:

rewindingcurrent: value 1
valid: 1
current: value 1
key: 0
key/value: [0 -> value 1]

next: value 2
current: value 2
valid: 1
current: value 2
key: 1
key/value: [1 -> value 2]

next: value 3
current: value 3
valid: 1
current: value 3
key: 2
key/value: [2 -> value 3]

next:current:valid:

Шаблоны

Шаблоны - это способ описания лучших вариантов решения задач. Они дают гибкие решения общих задач программирования.

Factory

Шаблон Factory обеспечивает возможность создания экземпляра объекта во время выполнения. Он называется Factory Pattern ("Фабричный шаблон"), поскольку он отвечает за "изготовление" объекта. Метод Factory с параметрами получает в качестве аргумента имя класса для получения экземпляра класса.

Метод Factory с параметрами

<?php
class Example
{
// Метод Factory с параметрами
public static function factory($type)
{
if (include_once 'Drivers/' . $type . '.php') {
$classname = 'Driver_' . $type;
return new $classname;
} else {
throw new Exception ('Driver not found');
}
}
}
?>

Определение этого метода в классе позволяет загружать драйверы "на лету". Если класс Example является абстрактным классом базы данных, загрузка драйверов MySQL и SQLite может быть выполнена следующим образом:

<?php 
// Load a MySQL Driver
$mysql = Example::factory('MySQL');

// Load a SQLite Driver
$sqlite = Example::factory('SQLite');
?>

Singleton

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

Функция Singleton

<?php
class Example
{
// Hold an instance of the class
private static $instance;

// private конструктор; предотвращает прямое создание объекта
private function __construct()
{
echo 'I am constructed';
}

// Метод singleton
public static function singleton()
{
if (!isset(self::$instance)) {
$c = __CLASS__;
self::$instance = new $c;
}

return self::$instance;
}

// Метод Example
public function bark()
{
echo 'Woof!';
}

// Предотвращаем клонирование экземпляра пользователем
public function __clone()
{
trigger_error('Clone is not allowed.', E_USER_ERROR);
}

}

?>

Это позволяет восстановление единственного экземпляра класса Example

<?php 
// Это приведет к ошибке, потому что конструктор имеет статус private
$test = new Example;

// Это позволит восстановить единственный экземпляр класса$test = Example::singleton();
$test->bark();

// Это выдаст ошибку E_USER_ERROR.
$test_clone = clone($test);

?>

"Волшебные" методы

Имена функций:

__construct
__destruct
__call
__get
__set
__isset
__unset
__sleep
__wakeup
__toString
__set_state
__clone and
__autoload

являются "волшебными" в классах PHP. Эти имена не могут быть присвоены любым классам без ассоциации с "волшебной" функциональностью.

Предостережение

В PHP все имена функций, начинающиеся с __, резервируются, как "волшебные". Не рекомендуется использовать имена, начинающиеся с __, для целей, отличающихся от задокументированной "волшебной" функциональности.

__sleep и __wakeup

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

Обычным использованием __sleep является закрытие соединений с любыми базами данных объекта, завершение операций и т.п. Также функция полезна в случае работы с большими объектами, в полном сохранении которых нет необходимости.

В противоположность, unserialize() проверяет присутствие функции с волшебным именем __wakeup. Если такая функция присутствует, она может реконструировать ресурсы, которые может иметь объект.

Надлежащим использованием __wakeup является восстановление соединений с базой данных, которые могли быть потеряны в процессе сериализации, и выполнение других задач реинициализации.

Sleep и wakeup

<?php
class Connection {
protected $link;
private $server, $username, $password, $db;

public function __construct($server, $username, $password, $db)
{
$this->server = $server;
$this->username = $username;
$this->password = $password;
$this->db = $db;
$this->connect();
}

private function connect()
{
$this->link = mysql_connect($this->server, $this->username, $this->password);
mysql_select_db($this->db, $this->link);
}

public function __sleep()
{
mysql_close($this->link);
}

public function __wakeup()
{
$this->connect();
}
}
?>

__toString

Метод __toString позволяет классу решить, как он будет реагировать при преобразовании в строку.

Простой пример

<?php  
// Объявляем простой класс
class TestClass
{
public $foo;

public function __construct($foo) {
$this->foo = $foo;
}

public function __toString() {
return $this->foo;
}
}

$class = new TestClass('Hello');
echo $class;
?>

Результат выполнения данного примера:

Hello

Не имеет значения, что до PHP 5.2.0 метод __toString вызывался только тогда, когда он был прямо объединен с echo() или print().

__set_state

Этот статический метод вызывается для классов, экспортированных функцией var_export(), начиная с PHP 5.1.0.

Единственным параметром этого метода является массив, содержащий экспортированные свойства в форме массива array('property' => value, ...).

Final

Ключевое слово "final"

Разместив перед объявлениями методов или свойств класса ключевое слово "final", вы можете предотвратить их переопределение в дочерних классах.

Пример окончательных (final) методов

<?php
class BaseClass {
public function test() {
echo "Вызван метод BaseClass::test()\n";
}

final public function moreTesting() {
echo "Вызван метод BaseClass::moreTesting()\n";
}
}

class ChildClass extends BaseClass {
public function moreTesting() {
echo "Вызван метод ChildClass::moreTesting()\n";
}
}
// Выполнение заканчивается фатальной ошибкой: Cannot override final method BaseClass::moreTesting()
// (Метод BaseClass::moretesting() не может быть переопределён)
?>

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

Создание копии объекта с абсолютно идентичными свойствами не всегда является приемлемым вариантом. Хорошим примером необходимости копирования конструкторов может послужить ситуация, когда у вас есть объект, представляющий собой окно GTK и содержащий ресурс-идентификатор этого окна; когда вы создаете копию этого объекта, вам может понадобиться, чтобы копия объекта содержала ресурс-идентификатор нового окна. Другим примером может послужить ситуация, когда ваш объект содержит ссылку на какой-либо другой используемый объект и, когда вы создаёте копию ссылающегося объекта, вам нужно также создать новый экземпляр содержащегося объекта, так, чтобы копия объекта содержала собственный отдельный экземпляр содержащегося объекта.

Копия объекта создается с использованием вызова clone (который вызывает метод __clone() объекта, если это возможно). Вызов метода __clone() не может быть осуществлён непосредственно

$copy_of_object = clone $object;

Когда программист запрашивает создание копии объекта, PHP5 определит, был ли для этого объекта объявлен метод __clone() или нет. Если нет, будет вызван метод __clone(), объявленный по умолчанию, который скопирует все свойства объекта. Если метод __clone() был объявлен, создание копий свойств в копии объекта полностью возлагается на него. Для удобства, движок обеспечивает программиста функцией, которая импортирует все свойства из объекта-источника, так что программист может осуществить позначное копирование свойств и переопределять только необходимые.

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

<?php
class MyCloneable {
static $id = 0;

function MyCloneable() {
$this->id = self::$id++;
}

function __clone() {
$this->address = "Москва";
$this->id = self::$id++;
}
}

$obj = new MyCloneable();

$obj->name = "Привет";
$obj->address = "Самара";

print $obj->id . "\n";

$obj_cloned = clone $obj;

print $obj_cloned->id . "\n";
print $obj_cloned->name . "\n";
print $obj_cloned->address . "\n";
?>

Сравнение объектов

В PHP5 сравнение объектов является более сложным процессом, чем в PHP4, а также процессом, более соответствующим идеологии объектно-ориентированного языка (здесь не имеется в виду, что PHP 5 является таковым).

При использовании оператора сравнения (==), свойства объектов просто сравниваются друг с другом, а именно: два объекта равны, если они содержат одинаковые свойства и одинаковые их значения и являются экземплярами одного и того же класса.

С другой стороны, при использовании оператора идентичности (===), свойства объекта считаются идентичными тогда и только тогда, когда они ссылаются на один и тот же экземпляр одного и того же класса.

Следующий пример внесёт ясность.

Пример сравнения объектов в PHP5

<?php
function bool2str($bool) {
if ($bool === false) {
return 'FALSE';
} else {
return 'TRUE';
}
}

function compareObjects(&$o1, &$o2) {
echo 'o1 == o2 : '.bool2str($o1 == $o2)."\n";
echo 'o1 != o2 : '.bool2str($o1 != $o2)."\n";
echo 'o1 === o2 : '.bool2str($o1 === $o2)."\n";
echo 'o1 !== o2 : '.bool2str($o1 !== $o2)."\n";
}

class Flag {
var $flag;

function Flag($flag=true) {
$this->flag = $flag;
}
}

class OtherFlag {
var $flag;

function OtherFlag($flag=true) {
$this->flag = $flag;
}
}

$o = new Flag();
$p = new Flag();
$q = $o;
$r = new OtherFlag();

echo "Два экземпляра одного и того же класса\n";
compareObjects($o, $p);

echo "\nДве ссылки на один и тот же экземпляр\n";
compareObjects($o, $q);

echo "\nЭкземпляры двух разных классов\n";
compareObjects($o, $r);
?>

Результатом выполнения этого кода будет:

Два экземпляра одного и того же класса
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : FALSE
o1 !== o2 : TRUE

Две ссылки на один и тот же экземпляр
o1 == o2 : TRUE
o1 != o2 : FALSE
o1 === o2 : TRUE
o1 !== o2 : FALSE

Экземпляры двух разных классов
o1 == o2 : FALSE
o1 != o2 : TRUE
o1 === o2 : FALSE
o1 !== o2 : TRUE

Отражение

PHP 5 содержит комплект API отражений, который дает возможность восстанавливать исходную информацию о классах, интерфейсах, функциях и методах равно как и расширения. Кроме того, API отражений также позволяет восстанавливать комментарии документации к функциям, классам и методам.

API отражения является объектно-ориентированным расширением Zend Engine, состоящим из следующих классов:

<?php
class Reflection { }
interface Reflector { }
class ReflectionException extends Exception { }
class ReflectionFunction implements Reflector { }
class ReflectionParameter implements Reflector { }
class ReflectionMethod extends ReflectionFunction { }
class ReflectionClass implements Reflector { }
class ReflectionObject extends ReflectionClass { }
class ReflectionProperty implements Reflector { }
class ReflectionExtension implements Reflector { }
?>

Пример. Стандартное применение API отражения

<?php
Reflection::export(new ReflectionClass('Exception'));
?>

Результат выполнения данного примера:

Class [  class Exception ] {
- Constants [0] {
}

- Static properties [0] {
}

- Static methods [0] {
}

- Properties [6] {
Property [ protected $message ]
Property [ private $string ]
Property [ protected $code ]
Property [ protected $file ]
Property [ protected $line ]
Property [ private $trace ]
}

- Methods [9] {
Method [ final private method __clone ] {
}

Method [ public method __construct ] {

- Parameters [2] {
Parameter #0 [ $message ]
Parameter #1 [ $code ]
}
}

Method [ final public method getMessage ] {
}

Method [ final public method getCode ] {
}

Method [ final public method getFile ] {
}

Method [ final public method getLine ] {
}

Method [ final public method getTrace ] {
}

Method [ final public method getTraceAsString ] {
}

Method [ public method __toString ] {
}
}
}

ReflectionException

ReflectionException расширяет стандартный класс Exception и генерируется API отражений. Не содержит каких-либо специфических методов или свойств.

ReflectionFunction

Класс ReflectionFunction позволяет восстанавливать исходную информацию о функции

<?php
class ReflectionFunction implements Reflector
{
final private __clone()
public object __construct(string name)
public string __toString()
public static string export(string name, bool return)
public string getName()
public bool isInternal()
public bool isUserDefined()
public string getFileName()
public int getStartLine()
public int getEndLine()
public string getDocComment()
public array getStaticVariables()
public mixed invoke(mixed args)
public mixed invokeArgs(array args)
public bool returnsReference()
public ReflectionParameter[] getParameters()
public int getNumberOfParameters()
public int getNumberOfRequiredParameters()
}
?>

Замечание: getNumberOfParameters() И getNumberOfRequiredParameters() были добавлены в PHP 5.0.3, а invokeArgs() - в PHP 5.1.0.

Для разбора функции сначала придется создать экземпляр класса ReflectionFunction. Затем станет возможным вызов любого из вышеприведенных методов этого экземпляра.

Пример. Использование класса ReflectionFunction

<?php
/**
* Простой счетчик
*
* @return int
*/
function counter()
{
static $c = 0;
return $c++;
}

// Создаем экземпляр класса Reflection_Function
$func = new ReflectionFunction('counter');

// Выводим основную информациюprintf(
"===> The %s function '%s'\n".
" declared in %s\n".
" lines %d to %d\n",
$func->isInternal() ? 'internal' : 'user-defined',
$func->getName(),
$func->getFileName(),
$func->getStartLine(),
$func->getEndline()
);

// Выводим комментарии документации
printf("---> Documentation:\n %s\n", var_export($func->getDocComment(), 1));

// Выводим статические переменные при их наличии
if ($statics = $func->getStaticVariables())
{
printf("---> Static variables: %s\n", var_export($statics, 1));
}

// Вызываем функциюprintf("---> Invokation results in: ");
var_dump($func->invoke());

// можем предпочесть метод export()
echo "\nReflectionFunction::export() results:\n";
echo ReflectionFunction::export('counter');
?>

Замечание: метод invoke() принимает переменное число аргументов, которые передаются функцииon таким же образом, как и в call_user_func().

ReflectionParameter

Класс ReflectionParameter восстанавливает информацию о параметрах функций и методов

<?php
class ReflectionParameter implements Reflector
{
final private __clone()
public object __construct(string name)
public string __toString()
public static string export(mixed function, mixed parameter, bool return)
public string getName()
public bool isPassedByReference()
public ReflectionFunction getDeclaringFunction()
public ReflectionClass getDeclaringClass()
public ReflectionClass getClass()
public bool isArray()
public bool allowsNull()
public bool isPassedByReference()
public bool getPosition()
public bool isOptional()
public bool isDefaultValueAvailable()
public mixed getDefaultValue()
}
?>

Замечание: getDefaultValue(), isDefaultValueAvailable() и isOptional() были добавлены в PHP 5.0.3, а isArray() - в PHP 5.1.0. getDeclaringFunction() and getPosition() были добавлены в PHP 5.1.3.

Для разбора параметров функции сначала создается экземпляр класса ReflectionFunction или ReflectionMethod, а затем используются их метод getParameters() для получения массива параметров.

Пример. Использование класса ReflectionParameters

<?php
function foo($a, $b, $c) { }
function bar(Exception $a, &$b, $c) { }
function baz(ReflectionFunction $a, $b = 1, $c = null) { }
function abc() { }

// Создаем экземпляр класса Reflection_Function с
// параметром, заданным из коммандной строки $reflect = new ReflectionFunction($argv[1]);

echo $reflect;

foreach ($reflect->getParameters() as $i => $param) {
printf(
"-- Parameter #%d: %s {\n".
" Class: %s\n".
" Allows NULL: %s\n".
" Passed to by reference: %s\n".
" Is optional?: %s\n".
"}\n",
$i,
$param->getName(),
var_export($param->getClass(), 1),
var_export($param->allowsNull(), 1),
var_export($param->isPassedByReference(), 1),
$param->isOptional() ? 'yes' : 'no'
);
}
?>

ReflectionClass

Класс ReflectionClass позволяет восстанавливать исходную информацию классов

<?php
class ReflectionClass implements Reflector
{
final private __clone()
public object __construct(string name)
public string __toString()
public static string export(mixed class, bool return)
public string getName()
public bool isInternal()
public bool isUserDefined()
public bool isInstantiable()
public bool hasConstant(string name)
public bool hasMethod(string name)
public bool hasProperty(string name)
public string getFileName()
public int getStartLine()
public int getEndLine()
public string getDocComment()
public ReflectionMethod getConstructor()
public ReflectionMethod getMethod(string name)
public ReflectionMethod[] getMethods()
public ReflectionProperty getProperty(string name)
public ReflectionProperty[] getProperties()
public array getConstants()
public mixed getConstant(string name)
public ReflectionClass[] getInterfaces()
public bool isInterface()
public bool isAbstract()
public bool isFinal()
public int getModifiers()
public bool isInstance(stdclass object)
public stdclass newInstance(mixed args)
public stdclass newInstanceArgs(array args)
public ReflectionClass getParentClass()
public bool isSubclassOf(ReflectionClass class)
public array getStaticProperties()
public mixed getStaticPropertyValue(string name [, mixed default])
public void setStaticPropertyValue(string name, mixed value)
public array getDefaultProperties()
public bool isIterateable()
public bool implementsInterface(string name)
public ReflectionExtension getExtension()
public string getExtensionName()
}
?>

Замечание: hasConstant(), hasMethod(), hasProperty(), getStaticPropertyValue() и setStaticPropertyValue() были введены в PHP 5.1.0, а newInstanceArgs() - в PHP 5.1.3.

Для восстановления информации класса сперва создается экземпляр класса ReflectionClass. Затем можно вызывать любой из вышеприведенных методов этого экземпляра.

Пример. Использование класса ReflectionClass

<?php
interface Serializable
{
// ...
}

class Object
{
// ...
}

/**
* class счетчика
*/
class Counter extends Object implements Serializable
{
const START = 0;
private static $c = Counter::START;

/**
* Вызываем счетчик
*
* @access public
* @return int
*/
public function count() {
return self::$c++;
}
}

// Создаем экземпляр класса ReflectionClass$class = new ReflectionClass('Counter');

// Выводим базовую информациюprintf(
"===> The %s%s%s %s '%s' [extends %s]\n" .
" declared in %s\n" .
" lines %d to %d\n" .
" having the modifiers %d [%s]\n",
$class->isInternal() ? 'internal' : 'user-defined',
$class->isAbstract() ? ' abstract' : '',
$class->isFinal() ? ' final' : '',
$class->isInterface() ? 'interface' : 'class',
$class->getName(),
var_export($class->getParentClass(), 1),
$class->getFileName(),
$class->getStartLine(),
$class->getEndline(),
$class->getModifiers(),
implode(' ', Reflection::getModifierNames($class->getModifiers()))
);

// Выводим комментарии документации
printf("---> Documentation:\n %s\n", var_export($class->getDocComment(), 1));

// Какие интерфейсы задействованы классом
printf("---> Implements:\n %s\n", var_export($class->getInterfaces(), 1));

// Константы класса
printf("---> Constants: %s\n", var_export($class->getConstants(), 1));

// Свойства класса
printf("---> Properties: %s\n", var_export($class->getProperties(), 1));

// Методы
printf("---> Methods: %s\n", var_export($class->getMethods(), 1));

// Если класс позволяет, создаем его экземпляр
if ($class->isInstantiable()) {
$counter = $class->newInstance();

echo '---> $counter is instance? ';
echo $class->isInstance($counter) ? 'yes' : 'no';

echo "\n---> new Object() is instance? ";
echo $class->isInstance(new Object()) ? 'yes' : 'no';
}
?>

Замечание: метод newInstance() принимает переменное число аргументов, которые передаются функции так же, как и в call_user_func().

Замечание: выражения $class = new ReflectionClass('Foo'); $class->isInstance($arg) эквивалентны $arg instanceof Foo или is_a($arg, 'Foo').

ReflectionObject

Класс ReflectionObject позволяет восстанавливать исходную информацию объектов

<?php
class ReflectionObject extends ReflectionClass
{
final private __clone()
public object __construct(mixed object)
public string __toString()
public static string export(mixed object, bool return)
}
?>

ReflectionMethod

Класс ReflectionMethod позволяет восстанавливать исходную информацию методов класса

<?php
class ReflectionMethod extends ReflectionFunction
{
public __construct(mixed class, string name)
public string __toString()
public static string export(mixed class, string name, bool return)
public mixed invoke(stdclass object, mixed args)
public mixed invokeArgs(stdclass object, array args)
public bool isFinal()
public bool isAbstract()
public bool isPublic()
public bool isPrivate()
public bool isProtected()
public bool isStatic()
public bool isConstructor()
public bool isDestructor()
public int getModifiers()
public ReflectionClass getDeclaringClass()

// Inherited from ReflectionFunction
final private __clone()
public string getName()
public bool isInternal()
public bool isUserDefined()
public string getFileName()
public int getStartLine()
public int getEndLine()
public string getDocComment()
public array getStaticVariables()
public bool returnsReference()
public ReflectionParameter[] getParameters()
public int getNumberOfParameters()
public int getNumberOfRequiredParameters()
}
?>

Для исследования метода сначала создается экземпляр класса ReflectionMethods. Затем можно вызывать любой из вышеприведенных методов этого экземпляра.

Пример. Использование класса ReflectionMethod

<?php
class Counter
{
private static $c = 0;

/**
* Инкрементный счетчик
*
* @final
* @static
* @access public
* @return int
*/
final public static function increment()
{
return ++self::$c;
}
}

// Создаем экземпляр класса Reflection_Method
$method = new ReflectionMethod('Counter', 'increment');

// Выводим основную информацию
printf(
"===> The %s%s%s%s%s%s%s method '%s' (which is %s)\n" .
" declared in %s\n" .
" lines %d to %d\n" .
" having the modifiers %d[%s]\n",
$method->isInternal() ? 'internal' : 'user-defined',
$method->isAbstract() ? ' abstract' : '',
$method->isFinal() ? ' final' : '',
$method->isPublic() ? ' public' : '',
$method->isPrivate() ? ' private' : '',
$method->isProtected() ? ' protected' : '',
$method->isStatic() ? ' static' : '',
$method->getName(),
$method->isConstructor() ? 'the constructor' : 'a regular method',
$method->getFileName(),
$method->getStartLine(),
$method->getEndline(),
$method->getModifiers(),
implode(' ', Reflection::getModifierNames($method->getModifiers()))
);

// Комментарии
printf("---> Documentation:\n %s\n", var_export($method->getDocComment(), 1));

// Статические переменные при их наличии
if ($statics= $method->getStaticVariables()) {
printf("---> Static variables: %s\n", var_export($statics, 1));
}

// Вызов метода
printf("---> Invokation results in: ");
var_dump($method->invoke(NULL));
?>

Замечание: При попытке вызова частного (private), защищенного (protected) или абстрактного (abstract) метода результатом будет исключение, генерируемое методом invoke().

Замечание: для статичных (static) методов, как видно выше, следует передавать NULL в качестве первого аргумента invoke(). Не статичным методам передаются экземпляры класса.

ReflectionProperty

Класс ReflectionProperty позволяет восстанавливать исходную информацию свойств класса.

<?php
class ReflectionProperty implements Reflector
{
final private __clone()
public __construct(mixed class, string name)
public string __toString()
public static string export(mixed class, string name, bool return)
public string getName()
public bool isPublic()
public bool isPrivate()
public bool isProtected()
public bool isStatic()
public bool isDefault()
public int getModifiers()
public mixed getValue(stdclass object)
public void setValue(stdclass object, mixed value)
public ReflectionClass getDeclaringClass()
public string getDocComment()
}
?>

Замечание: getDocComment() был добавлен в PHP 5.1.0.

Для получения информации свойства сначала создается экземпляр класса ReflectionProperty. Затем становится возможным вызов любого из вышеприведенных методов этого экземпляра.

Пример. Применение класса ReflectionProperty

<?php
class String
{
public $length = 5;
}

// Создание экземпляра класса ReflectionProperty
$prop = new ReflectionProperty('String', 'length');

// Вывод базовой информации
printf(
"===> The%s%s%s%s property '%s' (which was %s)\n" .
" having the modifiers %s\n",
$prop->isPublic() ? ' public' : '',
$prop->isPrivate() ? ' private' : '',
$prop->isProtected() ? ' protected' : '',
$prop->isStatic() ? ' static' : '',
$prop->getName(),
$prop->isDefault() ? 'declared at compile-time' : 'created at run-time',
var_export(Reflection::getModifierNames($prop->getModifiers()), 1)
);

// Создание экземпляра класса String
$obj= new String();

// Получение текущего значения
printf("---> Value is: ");
var_dump($prop->getValue($obj));

// Изменение значения
$prop->setValue($obj, 10);
printf("---> Setting value to 10, new value is: ");
var_dump($prop->getValue($obj));

// Дамп объекта
var_dump($obj);
?>

Замечание: Попытка получить или задать значение свойства класса типовы private или protected приведет к генерации исключения.

ReflectionExtension

Класс ReflectionExtension позволяет восстанавливать исходную информацию расширений. Вы можете восстановить все загруженные расширения во время исполнения сценария используя get_loaded_extensions()

<?php
class ReflectionExtension implements Reflector {
final private __clone()
public __construct(string name)
public string __toString()
public static string export(string name, bool return)
public string getName()
public string getVersion()
public ReflectionFunction[] getFunctions()
public array getConstants()
public array getINIEntries()
public ReflectionClass[] getClasses()
public array getClassNames()
}
?>

Для получения информации расширения сначала вызывается экземпляр класса ReflectionExtension. Затем становится возможным вызов любого из вышеперечисленных методов этого экземпляра.

Пример. Использование класса ReflectionExtensions

<?php 
// Create an instance of the ReflectionProperty class
$ext = new ReflectionExtension('standard');

// Print out basic information
printf(
"Name : %s\n" .
"Version : %s\n" .
"Functions : [%d] %s\n" .
"Constants : [%d] %s\n" .
"INI entries : [%d] %s\n" .
"Classes : [%d] %s\n",
$ext->getName(),
$ext->getVersion() ? $ext->getVersion() : 'NO_VERSION',
sizeof($ext->getFunctions()),
var_export($ext->getFunctions(), 1),

sizeof($ext->getConstants()),
var_export($ext->getConstants(), 1),

sizeof($ext->getINIEntries()),
var_export($ext->getINIEntries(), 1),

sizeof($ext->getClassNames()),
var_export($ext->getClassNames(), 1)
);
?>

Расширение классов отражения

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

Пример. Расширение встроенных классов

<?php
/**
* My Reflection_Method class
*/
class My_Reflection_Method extends ReflectionMethod
{
public $visibility = '';

public function __construct($o, $m)
{
parent::__construct($o, $m);
$this->visibility= Reflection::getModifierNames($this->getModifiers());
}
}

/**
* Demo class #1
*
*/
class T {
protected function x() {}
}

/**
* Demo class #2
*
*/
class U extends T {
function x() {}
}

// Вывод информации
var_dump(new My_Reflection_Method('U', 'x'));
?>

Замечание: Если вы переписываете конструктор, не забывайте вызывать родительский конструктор _before_ перед любым кодом, который вы вставляете.

Несоблюдение этого условия вызовет ошибку: Fatal error: Internal error: Failed to retrieve the reflection object

Extends

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

Унаследованные методы и члены могут быть переопределены путем переобъявления их под тем же именем, определенным в базовом классе. К переопределенному методу или члену можно получить доступ, ссылаясь на него с помощью parent::

Простое наследование класса

<?php
class ExtendClass extends SimpleClass
{
// Переопределим родительский метод
function displayVar()
{
echo "Расширяемый класс\n";
parent::displayVar();
}
}

$extended = new ExtendClass();
$extended->displayVar();
?>

Результат выполнения данного примера:

Расширяемый класс дефолтное значение

Указание типов (Type Hints)

В PHP 5 появилась новая возможность, обозначенная как Type Hinting, что можно перевести как "указание типов".

Теперь при определении функции можно указать тип ее параметра, как объект (путем указания имени класса в определении функции) или массив (начиная с PHP 5.1).

Пример. Пример Type Hinting

<?php  
// Пример класса
class MyClass
{
/**
* Тестовая функция
*
* Указываем, что первый параметр должен быть объектом типа OtherClass
*/
public function test(OtherClass $otherclass) {
echo $otherclass->var;
}


/**
* Другая тестовая функция
*
* Указываем, что первый параметр должен быть массивом
*/
public function test_array(array $input_array) {
print_r($input_array);
}
}

// Другой пример класса
class OtherClass {
public $var = 'Hello World';
}
?>

Теперь несоответствие указанному типу приводит к фатальной ошибке

<?php
// Экземпляр каждго класса
$myclass = new MyClass;
$otherclass = new OtherClass;

// Fatal Error: Первый аргумент должен быть объектом класса OtherClass
$myclass->test('hello');

// Fatal Error: Первый аргумент должен быть экземпляром OtherClass
$foo = new stdClass;
$myclass->test($foo);

// Fatal Error: Первый аргумент не должен быть null$myclass->test(null);

// Работает: выводит "Hello World"$myclass->test($otherclass);

// Fatal Error: Аргумент 1 должен быть массивом
$myclass->test_array('a string');

// работает: выводит массив$myclass->test_array(array('a', 'b', 'c'));
?>

Указание типов работает и с функциями:

<?php  
// Пример класса
class MyClass {
public $var = 'Hello World';
}

/**
* Тестовая функция
*
* Первый параметр должен быть объектом типа MyClass
*/
function MyFunction (MyClass $foo) {
echo $foo->var;
}

// Работает$myclass = new MyClass;
MyFunction($myclass);
?>

Указание типов может работать только с типами object или array (начиная с PHP 5.1). Указание типов int и string не поддерживается.

New

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

Создание экземпляра класса

<?php
$instance = new SimpleClass();
?>

При присвоении уже существующего экземпляра класса новой переменной, новая переменная получит доступ к этому же экземпляру, как к объекту, который был присвоен. Это такое же поведение, как и при передаче экземпляра класса функции. Копия уже созданного объекта может быть получена путем его клонирования.

Присвоение объекта

<?php
$assigned = $instance;
$reference =& $instance;

$instance->var = '$assigned получит это значение';

$instance = null; // $instance и $reference станут null
var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>

Результат выполнения данного примера:

NULL
NULL
object(SimpleClass)#1 (1) {
["var"]=>
string(30) "$assigned получит это значение"
}

Автоматически загружающиеся объекты

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

В PHP5 это больше не является необходимостью. Можно определить функцию __autoload, которая автоматически вызывается при попытке использования класса, который еще не определен. При вызове такой функции движку скрипта дается последний шанс загрузить класс перед тем, как скрипт должен будет аварийно завершиться с генерацией ошибки.

Замечание: исключения, генерируемые в функции __autoload, не могут быть перехвачены в блоке перехвата, и генерируют фатальную ошибку.

Пример автозагрузки.

Этот пример пытается загрузить классы MyClass1 и MyClass2 из файлов MyClass1.php и MyClass2.php соответственно

<?php
function __autoload($class_name) {
require_once $class_name . '.php';
}

$obj = new MyClass1();
$obj2 = new MyClass2();
?>

Конструкторы и деструкторы

Конструктор

PHP 5 позволяет объявлять методы-конструкторы. Классы, в которых объявлен метод-констуктор, будут вызывать этот метод при каждом создании нового объекта, так что это может оказаться полезным, чтобы, например, инициализировать какое-либо состояние объекта перед его использованием.

Замечание: Конструкторы в классах-родителях не вызываются автоматически. Чтобы вызвать конструктор, объявленный в родительском классе, следует обратиться к методу parent::__construct().

Пример. Использование унифицированных конструкторов

<?php
class BaseClass {
function __construct() {
print "Конструктор класса BaseClass\n";
}
}

class SubClass extends BaseClass {
function __construct() {
parent::__construct();
print "Конструктор класса SubClass\n";
}
}

$obj = new BaseClass();
$obj = new SubClass();
?>

Если PHP 5 не может обнаружить объявленный метод __construct(), вызов конструктора произойдет по прежней схеме, через обращение к методу, имя которого соответствует имени класса. Может возникнуть только одна проблема совместимости старого кода, если в нём присутствуют классы с методами __construct().

Деструкторы

PHP 5 предоставляет концепцию деструкторов, сходную с теми, что применяются в других ОО языках, таких, как Java: когда освобождается последняя ссылка на объект, перед высвобождением памяти, занимаемой этим объектом, вызывается метод __destruct(), не принимающий параметров.

Пример. Пример использования деструктора

<?php
class MyDestructableClass {
function __construct() {
print "Конструктор\n";
$this->name = "MyDestructableClass";
}

function __destruct() {
print "Уничтожается " . $this->name . "\n";
}
}

$obj = new MyDestructableClass();
?>

Как и в случае с конструкторами, деструкторы, объявленные в родительском классе, не будут вызваны автоматически. Для вызова деструктора, объявленном в классе-родителе, следует обратиться к методу parent::__destruct().

Видимость

Видимость свойства или метода может быть определена путем префиксирования объявления ключевыми словами public, protected или private. К элементам, объявленным public, доступ возможен отовсюду. Protected ограничивает доступ до унаследованных и родительских классов (и до класса, который определяет элемент). Private ограничивает видимость только границами класса, в котором элемент определен.

Члены класса должны быть определены с ключевыми словами public, private или protected.

Объявление члена

<?php
/**
* Define MyClass
*/
class MyClass
{
public $public = 'Public';
protected $protected = 'Protected';
private $private = 'Private';

function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}
$obj = new MyClass();
echo $obj->public; // Работает
echo $obj->protected; // Фатальная ошибка
echo $obj->private; // Fatal Error
$obj->printHello(); // Показывает Public, Protected and Private


/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// Мы можем переобъявить public или protected метод, но не private
protected $protected = 'Protected2';

function printHello()
{
echo $this->public;
echo $this->protected;
echo $this->private;
}
}

$obj2 = new MyClass2();
echo $obj->public; // Работает
echo $obj2->private; // Undefined
echo $obj2->protected; // Fatal Error
$obj2->printHello(); // Показывает Public, Protected2 или Private
?>

Замечание: Способ объявления переменной в PHP4 с помощью ключевого слова var все еще поддерживается по причинам совместимости (как синоним ключевого слова public). В PHP5 до 5.1.3, ключевое слово var будет генерировать предупреждение E_STRICT.

Видимость метода.

Методы класса должны быть определены с помощью public, private или protected. Методы без любого из этих ключевых слов определяются, как public.

Определение метода

<?php
/**
* Define MyClass
*/
class MyClass
{
// Contructors must be public
public function __construct() { }

// Declare a public method
public function MyPublic() { }

// Declare a protected method
protected function MyProtected() { }

// Declare a private method
private function MyPrivate() { }

// This is public
function Foo()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate();
}
}

$myclass = new MyClass;$myclass->MyPublic(); // Работает
$myclass->MyProtected(); // Fatal Error
$myclass->MyPrivate(); // Fatal Error
$myclass->Foo(); // Public, Protected и Private работают

/**
* Define MyClass2
*/
class MyClass2 extends MyClass
{
// This is public
function Foo2()
{
$this->MyPublic();
$this->MyProtected();
$this->MyPrivate(); // Fatal Error
}
}

$myclass2 = new MyClass2;
$myclass2->MyPublic(); // Работает
$myclass2->Foo2(); // Public и Protected работает, Private не работает
?>

Оператор ::

"Paamayim Nekudotayim" или просто "двойное двоеточие". Используя эту лексему, программист может обращаться к константам, статическим или перегруженным свойствам или методам класса.

При обращении к этим элементам извне класса, программист должен использовать имя этого класса.

"Paamayim Nekudotayim" на первый взгляд может показаться странным словосочетанием для обозначения двойного двоеточия. Однако, во время создания Zend Engine версии 0.5 (который входил в PHP3), Andi и Zeev выбрали именно это обозначение. "Paamayim Nekudotayim" действительно значит "двойное двоеточие". На иврите. Просто это обозначение не менялось ни разу в течение всего времени разработки PHP.

Использование :: вне объявления класса

<?php
class MyClass {
const CONST_VALUE = 'Значение константы';
}
echo MyClass::CONST_VALUE;
?>

Для обращения к свойствам и методам в объявлении класса используются ключевые слова self и parent.

Использование :: в объявлении класса

<?php
class OtherClass extends MyClass {
public static $my_static = 'статическая переменная';

public static function doubleColon() {
echo parent::CONST_VALUE . "\n";
echo self::$my_static . "\n";
}
}

OtherClass::doubleColon();
?>

Когда дочерний класс перегружает методы, объявленные в классе-родителе, PHP не будет осуществлять автоматический вызов методов, принадлежащих классу-родителю. Этот функционал возлагается на метод, перегружаемый в дочернем классе. Данное правило распространяется на конструкторы и деструкторы, перегруженные и "волшебные" методы.

Обращение к методу в родительском классе

<?php
class MyClass {

protected function myFunc() {
echo "MyClass::myFunc()\n";
}
}

class OtherClass extends MyClass {

/* Override parent's definition */
public function myFunc() {

/* But still call the parent function */
parent::myFunc();
echo "OtherClass::myFunc()\n";
}
}

$class = new OtherClass();
$class->myFunc();
?>

Static

Объявление членов или методов класса static делает их доступными без создания экземпляра класса. Член, объявленный static, не может быть доступен из объекта - экземпляра класса (хотя статический метод может).

Объявление static должно следовать после объявления типа видимости. Для совместимости с PHP4, при отсутствии объявления типа видимости, члены и методы работают, как public.

Поскольку методы static возможно вызывать без создания экземпляров объектов, псевдо-переменная $this недоступна внутри метода, объявленного как static.

Фактически вызовы статических методов совершаются во время компиляции. При использовании определенного имени класса метод уже полностью идентифицирован и не применяет правил наследования. Если вызов осуществляется посредством self, то self передается в текущий класс, то есть класс, которому принадлежит код. Здесь правила наследования также не применимы.

Доступ к статическим свойствам в объекте не может быть получен оператором ->.

Вызывающие не статические методы статически генерируют предупреждение уровня E_STRICT.

Пример статического члена

<?php
class Foo
{
public static $my_static = 'foo';

public function staticValue() {
return self::$my_static;
}
}

class Bar extends Foo
{
public function fooStatic() {
return parent::$my_static;
}
}

print Foo::$my_static . "\n";

$foo = new Foo();
print $foo->staticValue() . "\n";
print $foo->my_static . "\n"; // Не определенное "Property" my_static

// $foo::my_static невозможно
print Bar::$my_static . "\n";
$bar = new Bar();
print $bar->fooStatic() . "\n";
?>

Пример метода Static

<?php
class Foo {
public static function aStaticMethod() {
// ...
}
}

Foo::aStaticMethod();
?>

Константы в объектах

Константы также могут быть объявлены и в пределах одного класса. Отличие переменных и констант состоит в том, что при объявлении последних или при обращении к ним не используется символ $. Как и Разд. Static Keyword свойства и методы, значения констант, объявленных внутри класса, не могут быть получены через переменную, содержащую экземпляр этого класса.

Объявление и использование константы

<?php
class MyClass {
const constant = 'значение константы';

function showConstant() {
echo self::constant . "\n";
}
}

echo MyClass::constant . "\n";

$class = new MyClass();
$class->showConstant();
/* echo $class::constant; - такое использование недопустимо */
?>