Реверсный порядок очистки. Завершение скрипта - исключительная ситуация

Столько воды утекло с тех пор как ПХП провозгласил себя ООП языком. Мне не удалось уточнить когда именно, но «On July 13, 2004, PHP 5 was released, powered by the new Zend Engine II. PHP 5 included new features such as improved support for object-oriented programming».
Т.е теоретически уже тогда появились конструкторы и деструкторы. Сам пользуюсь версией 5.3.2, но диву даюсь что он вытворяет.

Немного о себе. Я программист С++, опыт 4 года. Специализация - компьютерная графика и работа с сетью. А именно создание сетевых игр. Но таким играм нужен сервер, а серверу база игроков. А игроки хотят еще и сайт. «Зачем нанимать веб-программиста, я ведь и сам неплох. Заодно и язык изучу.»
Так я думал пол года назад, но до сих пор не могу понять!

Классическое
Думаю многие кто работал с деструкторами однажды столкнулся:
PHP Fatal error: Exception thrown without a stack frame in Unknown on line 0
Первая реакция - недоумение. Вторая матная. И ведь впихивание exit() везде не дает результата, ведь догадка о том что эксепшен в деструкторе приходит далеко не сразу, а если он происходит, то скорее всего кодовая база значительная.
Ответ c bugs.php.net/bug.php?id=33598
[email protected]:
Throwing exceptions in __desctruct() is not allowed.
Should be documented..
[email protected]:
Thank you for the report, and for helping us make our documentation better.
"Attempting to throw an exception from a desctructor causes a fatal error."
Весело? Лично мне не очень.
Воспроизводится ошибка с неявным исключением очень просто.
class a
{
// ...
public function __destruct()
{
global $_SESSION;
$_SESSION = "Some information";
}
}
$dummy = new a();

^Примечание, явные исключения иногда работают верно. Буду ставить эксперементы что влияет.

Реверсный порядок очистки
Пусть в некоторой области видимости(глобальной и неочень) у нас следующий код:
$earth = new world();
$vasya = new human($earth);
Соответственно в коде конструктора человека идет его пришпандоривание к миру. (Предполагается что без мира вася существовать не будет, отбросим философию, нам проект скорее закрыть.)
Ну а в коде деструктора вася лежит аля $this->my_world->RemoveHumanFromWorld($this), в котором вызываются RemoveAllMyStuff, CalculateKarma и прочее. (Предположим у нас в мире не хранится ссылка на васю, так как это не требовалось в рамках задачи)
Что делает пхп при выходе из области видимости? Уничтожает мир и падает с ошибкой «Fatal error: Call to a member function RemoveHumanFromWorld() on a non-object in /home/god/my_projects/earth/creatures/reasonable/homo_sapiens.php on line 1956».
(Поэтому кстати мир был написан на С++, ведь богу не нужно что бы он запускался на любой вселенной. Виртуальный космос с сборщиком мусора. Ха Ха.)
Ответ с bugs.php.net/bug.php?id=36759
[email protected]
Fixed in CVS HEAD and PHP_5_2.
Найти бы этого дмитрия и ткнуть носом как в той картинке. Не знаю как там в последних версиях, пока не обновляюсь, но в 5.3 актуально.
Завершение скрипта - исключительная ситуация
Ох, про это столько можно писать. Глобальные переменные (аля сессий) перестают быть валидными, доступ к файловой системе обрубается. И вообще как я понимаю корректная работа скрипта не гарантируется. Так что не дай бог вам выполнять что то в деструкторе глобального обьекта…
Но это опустим. В документации пхп 5.1 и ранее (строфу и стих не найду) сказано: «The destructor method will be called as soon as there are no other references to a particular object», что в принципе логично для языка без строгого требования конструкции delete (лат. unset).
После баг репорта bugs.php.net/bug.php?id=38572
Документация изменилась «The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence»
Удобно? По мне - не очень.
Только прямые ссылки
Пусть согласно логике языка обьект $a должен удалиться раньше $b.
Но пусть в обьекте $b хранится ссылка на поле $a->some_data.
Тогда по логике обьект $b должен удалиться раньше. Увы, в пхп не так. Подобной баги я не нашел, но ситуация специфическая(и не побоюсь слова исключительная). Избегается несильным патчем до ссылки на $a, терпимо и репортить я не стал.
Дедлок в стиле пхп
$a->ref = $b;
$b->ref = $a;
Мне в свое время было интересно, как пхп справится с перекресными ссылками. Зависнет ли? Упадет ли с ошибкой «нельзя покинуть область видимости», и как она будет звучать на английском. Увы, теплица показала - все переменные будут существовать до конца, пока не придет как говорится «or in any order during the shutdown sequence»
Заключение
На данный момент все что я вспомнил. А может и все что я встречал. Но создалось впечатление что деструкторы в ПХП это костыль, так что скорее всего скоро опять наткнусь.
Мне кажется будущее ООП веб-программирование за интерпретатором c++, и возможно он будет готов. Возможно кто то однажды возьмется изменить с++, добавить стандартных конструкций что бы он стал ориентированным под веб. Но пока я альтернатив не нашел.

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

Конструктор класса PHP может содержать столько аргументов или параметров, сколько необходимо. Также можно определить конструктор вообще без параметров.

Важно отметить, что PHP-конструктор вызывается только один раз. И мы не можем вызвать конструктор явно, он вызывается автоматически при создании объекта.

Чтобы создать экземпляр класса, мы используем ключевое слово new, и в этот момент вызывается конструктор. В PHP родительский конструктор вызывается явно, если определен конструктор производного класса. Если требуется конструктор суперкласса, в производном классе нужен вызов parent::__construct() . Конструктор суперкласса может наследоваться, если производный класс не определяет конструктор сам. Наследуется конструктор так же, как и любой другой метод класса, если в суперклассе он не был объявлен как privat .

В PHP4 и ниже при определении конструктора использовался метод с таким же именем, что и класс (как в C++ и Java ). PHP5 ООП для определения конструктора любого класса содержит специальную функцию __construct() . Конструктор может содержать аргументы в соответствии с конкретными требованиями. Все объекты могут иметь встроенную функцию-конструктор, которая инициализирует при создании объекта его свойства. PHP позволяет создать только один конструктор для каждого класса, и мы не можем перегрузить конструктор.

Если существует несколько объявленных конструкторов, PHP будет вызывать первый и игнорировать остальные. Конструктор должен быть объявлен как public и может использоваться вне класса. Если конструктор объявлен как private , он может быть использован только с классом, в котором он определен. В случае если необходимо объявить его как private , нам нужно будет использовать вызов статической функции.

Так как конструктор является магическим методом, он будет иметь отличительную метку магических методов — два подчеркивания в начале. Для определения конструктора мы используем ключевое слово construct :

Construct(); // Конструктор без аргументов.

Пример PHP-конструктора

Давайте предположим, что у нас есть класс PHP Human , и он содержит переменную name . Исходный код будет выглядеть следующим образом:

Теперь мы добавим конструктор в этот класс. Помните, что в ООП PHP конструкторы — это магические методы, следовательно, они начинаются с двух символов подчеркивания и ключевого слова construct :

name= $nameofperson; } }

Создаем объекты этого класса с помощью ключевого слова new :

$firstObject = new Human("Joseph"); $secondObject= new Human("Taylor");

В этом ООП PHP примере мы создали объекты и инициализировали переменную $name . Первый объект будет хранить в переменной $name значение «Joseph «, а второй — «Taylor «. Мы присвоили объектам значения во время создания, чтобы избежать путаницы. Если бы для класса Human конструктор не был определен, то объекты создавались бы следующим образом:

$firstObject = new Human(); $secondObject = new Human();

PHP-конструктор с несколькими аргументами

Мы добавим новую переменную age в класс Human :

Сейчас наш класс содержит две переменные для хранения данных каждого объекта. Name и age представлены переменными $name и $age . Определяем конструктор еще раз, но на этот раз с двумя аргументами, один — чтобы установить экземпляр Name , а второй — age :

name= $nameofperson; $this -> age= $ageofperson; } } ?> $firstObject = new Human("Joseph", 23); $secondObject= new Human("Taylor", 35);

Мы создали два объекта, и они содержат начальные значения двух переменных-членов. Следует отметить, что согласно основам PHP ООП конструктор нельзя вызвать более одного раза, но мы можем изменять значения переменных-членов.

Поэтому в классе Human мы создадим два метода, один устанавливает возраст, а второй — имя. Конструктор используется, только чтобы задать начальные значения любой переменной. В дальнейшем, если нужно изменить это значение, мы будем использовать эти функции.

Тот же класс с двумя дополнительными методами:

name= $nameofperson; $this -> age= $ageofperson; } function set_name($newname){ $this ->name=$newname; } function set_age($newage){ $this ->age=$newage; } } ?>

Мы можем вызывать два новых метода, чтобы изменить имя или возраст. Добавим в пример еще два метода, чтобы вывести имя и возраст:

function get_name(){ return $this->name; } function get_age(){ return $this->age; }

Полный пример использования конструктора класса PHP ООП с четырьмя функциями:

name= $nameofperson; $this -> age= $ageofperson; } function set_name($newname){ $this ->name=$newname; } function set_age($newage){ $this ->age=$newage; } function get_name(){ return $this->name; } function get_age(){ return $this->age; } } ?>

Еще один пример PHP-конструктора

Сначала мы определяем конструктор для класса bookinfo . Когда будут созданы объекты, мы передадим в конструктор значения для соответствующих экземпляров.

Давайте посмотрим, как используется конструктор. Сначала создаем простой класс с одним свойством и одной функцией:

name; } } ?>

Это простой класс, который содержит одну переменную данных, название машины, и метод вывода этого названия.

Теперь добавим конструктор, который будет задавать значение name в момент создания объекта. В ООП PHP код конструктора будет выглядеть следующим образом:

function __construct($param){ $this->name=$param; }

Добавив в наш класс конструктор, мы получим следующий код:

name=$param; } public function get_carname() { echo $this->name; } } ?>

Теперь создаем из класса объект:

$mycar= new car(“Honda”);

Выводим значение данных переменной через вызов метода get_carname() :

$mycar->get_carname();

Результат будет следующим:

Honda.

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

Используем конструктор в примере объекта PHP ООП , который мы рассматривали ранее. В классе bookinfo мы добавляем следующий код:

function __construct($param1, $ param2){ $this->price=$param1; $this->title=$param2; }

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

$astrology= new bookinfo(25, “Up to date Astrology”); $programming= new bookinfo(36, “Up to date PHP”); $database= new bookinfo(17, “Up to date DB”);

Теперь объекты созданы и инициализированы с названиями и ценами на момент создания объекта, это достигается с помощью конструктора.

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

$astrology->getBookTitle(); $programming-> getBookTitle(); $database-> getBookTitle(); $astrology->getBookPrice(); $programming-> getBookPrice (); $database-> getBookPrice ();

Этот PHP-код дает следующий результат:

Up to date astrology
Up to date PHP
Up to date DB
25
36
17

Добро пожаловать во второй урок из серии, посвященной ООП. В первой статье вы ознакомились с основами ООП в PHP, включая понятия классов, методов, полей и объектов. Также вы узнали, как создать простенький класс и реализовать его.

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

Вот список того, о чем я расскажу вам в этой статье:

  • Конструкторы и деструкторы, которые позволяют назначить определенные действия объекту при его создании и удалении;
  • Статические поля и методы - это такие поля и методы, которые не связаны с конкретными объектами класса;
  • Константы класса, удобные для хранения фиксированных значений, относящихся к определенному классу;
  • Явное указание типа, используемое для задания ограничения типов параметров, которые можно передавать в тот или иной метод;
  • Специальные методы __get() и __set(), которые используются для задания и чтения значений полей классов;
  • Специальный метод __call(), применяемый для вызова метода класса.

Вы готовы? Тогда вперед!

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

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

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

На заметку: как удалить объект? PHP автоматически удаляет объект из памяти, когда не остается ни одной переменной, указывающей на него. Например, если вы создадите новый объект и сохраните его в переменной $myObject, а затем удалите ее с помощью метода unset($myObject), то сам объект также удалится. Также, если вы создали локальную переменную в какой-либо функции, она (вместе с объектом) удалится, когда функция завершит работу.

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

  • Конструктор вызывается сразу после того, как вы создали объект;
  • Деструктор вызывается строго перед тем, как объект удаляется из памяти.

Работа с конструкторами

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

Чтобы создать конструктор, добавьте в ваш класс специальный метод __construct() (перед словом construct - два символа подчеркивания). PHP автоматически вызовет этот метод при реализации вашего класса, то есть, при создании объекта этого класса.

Вот пример конструктора:

Class MyClass { public function __construct() { echo "I"ve just been created!"; } } $myObject = new MyClass(); // отобразит "I"ve just been created!"

В классе MyClass есть конструктор, который выводит на страницу строку "I"ve just been created!". Последняя строка кода создает новый объект класса MyClass. Когда это происходит, PHP автоматически вызывает конструктор, и сообщение отображается в браузере. Теперь на практике - инициализация полей класса:

Class Member { private $username; private $location; private $homepage; public function __construct($username, $location, $homepage) { $this->username = $username; $this->location = $location; $this->homepage = $homepage; } public function showProfile() { echo "

"; echo "
Username:
$this->username
"; echo "
Location:
$this->location
"; echo "
Homepage:
$this->homepage
"; echo "
"; } } $aMember = new Member("fred", "Chicago", "http://example.com/"); $aMember->showProfile();

Данный скрипт отобразит на странице следующее:

Username: fred Location: Chicago Homepage: http://example.com/

В нашем классе Member есть три поля и конструктор, который принимает в качестве параметров 3 значения - по одному для каждого поля. Конструктор назначит полям объекта значения, полученные в качестве аргументов. В классе также есть метод для отображения на странице значений полей объекта.

Затем в коде создается объект класса Member, в который мы передаем 3 значения "fred", "Chicago", и "http://example.com/", так как конструктор принимает именно 3 параметра. Конструктор записывает эти значения в поля созданного объекта. В завершение, вызывается метод showProfile() для созданного объекта, чтобы отобразить полученные значения.

Работа с деструкторами

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

Class MyClass { public function __destruct() { echo "I"m about to disappear - bye bye!"; // (очистить память) } } $myObject = new MyClass(); unset($myObject); // отобразит "I"m about to disappear - bye bye!"

Мы создали простенький деструктор, который отображает на странице сообщение. Затем мы создали объект нашего класса и сразу же удалили его, вызвав метод unset() для переменной, которая ссылается на объект. Перед самым удалением объекта вызвался деструктор, который отобразил в браузере сообщение "I"m about to disappear - bye bye!".

На заметку: в отличие от конструкторов, в деструкторы нельзя передавать никакие параметры.

Деструктор также вызывается при выходе из скрипта, так как все объекты и переменные при выходе из метода удаляются. Так, следующий код также вызовет деструктор:

Class MyClass { public function __destruct() { echo "I"m about to disappear - bye bye!"; // (очистить память) } } $myObject = new MyClass(); exit; // отобразит "I"m about to disappear - bye bye!"

Также, если работа скрипта прекратится из-за возникшей ошибки, деструктор тоже вызовется.

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

parent::__construct(). То же самое касается деструкторов. Вызвать деструктор родителя можно так: parent:__destruct(). Я расскажу вам о классах-родителях и наследниках в следующем уроке, посвященном наследованию.

Статические поля класса

Мы рассмотрели статические переменные в статье PHP Variable Scope: All You Need to Know. Как обычная локальная переменная, статическая переменная доступна только в пределах функции. Тем не менее, в отличие от обычных локальных, статические переменные сохраняют значения между вызовами функции.

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

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

Чтобы создать статическую переменную, добавьте ключевое слово static в ее задании:

Class MyClass { public static $myProperty; }

Вот пример того, как работают статические переменные:

Class Member { private $username; public static $numMembers = 0; public function __construct($username) { $this->username = $username; self::$numMembers++; } } echo Member::$numMembers . "
"; // отобразит "0" $aMember = new Member("fred"); echo Member::$numMembers . "
"; // отобразит "1" $anotherMember = new Member("mary"); echo Member::$numMembers . "
"; // отобразит "2"

Есть несколько интересных вещей, так что давайте разберем данный скрипт:

  • В классе Member два поля: частное поле $username и статическое $numMembers, которое изначально получает значение 0;
  • Конструктор получает в качестве параметра аргумент $username и устанавливает полю только что созданного объекта значение этого параметра. В то же время, он инкрементирует значение поля $numMembers, тем самым давая понять, что число объектов нашего класса увеличилось на 1.

Отметьте, что конструктор обращается к статическому полю так: self::$numMembers. Ключевое слово self похоже на $this, которое мы рассмотрели в прошлом уроке. Тогда как $this ссылается на текущий объект, self - на текущий класс. Также тогда как для получения доступа к полям и методам объекта вы используете ->, то в этом случае используйте:: для получения доступа к полям и методам класса.

  • В завершении скрипт создает несколько объектов класса Member и отображает на странице их количество, т.е. значение статической переменной $numMembers. Отметьте, что данная переменная сохраняет свое значение на протяжении всей работы скрипта, несмотря на объекты класса.

Итак, чтобы получить доступ к статическому полю класса, применяйте оператор::. Здесь мы не можем воспользоваться ключевым словом self, так как код находится за пределами класса, поэтому мы пишем имя класса, затем::, а затем имя поля (Member::$numMembers). В пределах конструктора тоже нужно использовать именно такую структуру, а не self.

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

Статические методы

Наряду со статическими полями класса, вы также можете создавать статические методы. Статические методы, так же как и поля, связаны с классом, но нет необходимости создавать объект класса, чтобы вызвать статический метод. Это делает такие методы полезными в случае, если вам нужен класс, который не оперирует реальными объектами.

Чтобы создать статический метод, нужно добавить в его объявлении ключевое слово static:

Class MyClass { public static function myMethod() { // (действия) } }

В нашем предыдущем примере, касающемся статических полей, было статическое поле $numMembers. Делать поля частными, а методы для доступа к ним - открытыми, - это хорошая практика. Давайте сделаем наше статическое поле частным и напишем статический метод public для получения значения данного поля:

Class Member { private $username; private static $numMembers = 0; public function __construct($username) { $this->username = $username; self::$numMembers++; } public static function getNumMembers() { return self::$numMembers; } } echo Member::getNumMembers() . "
"; // отобразит "0" $aMember = new Member("fred"); echo Member::getNumMembers() . "
"; // отобразит "1" $anotherMember = new Member("mary"); echo Member::getNumMembers() . "
"; // отобразит "2"

Здесь мы создали статический метод getNumMembers(), который возвращает значение статического поля $numMembers. Мы также сделали это поле частным, чтобы нельзя было получить его значение извне.

Мы также изменили код вызова, применив метод getNumMembers() для получения значения поля $numMembers. Отметьте, можно вызывать данный метод без того, чтобы создавать объект класса, потому что метод - статический.

Константы класса

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

Определить классовую константу можно с помощью ключевого слова const. Например:

Class MyClass { const CONSTANT_NAME = value; }

Обратиться в последствии к классовой константе можно через имя класса и оператор::. Например, так:

MyClass::CONSTANT_NAME

На заметку: как и в случае со статическими полями и методами, вы можете обратиться к константе через ключевое слово self.

Давайте рассмотрим классовые константы на примере. Добавим в класс Member константы, в которых будут храниться значения их роли (участник, модератор или администратор). Применив константы вместо обычных численных значений, мы сделали код более читабельным. Вот скрипт:

Class Member { const MEMBER = 1; const MODERATOR = 2; const ADMINISTRATOR = 3; private $username; private $level; public function __construct($username, $level) { $this->username = $username; $this->level = $level; } public function getUsername() { return $this->username; } public function getLevel() { if ($this->level == self::MEMBER) return "a member"; if ($this->level == self::MODERATOR) return "a moderator"; if ($this->level == self::ADMINISTRATOR) return "an administrator"; return "unknown"; } } $aMember = new Member("fred", Member::MEMBER); $anotherMember = new Member("mary", Member::ADMINISTRATOR); echo $aMember->getUsername() . " is " . $aMember->getLevel() . "
"; // отобразит "fred is a member" echo $anotherMember->getUsername() . " is " . $anotherMember->getLevel() . "
"; // отобразит "mary is an administrator"

Мы создали три классовые константы: MEMBER, MODERATOR и ADMINISTRATOR, и задали им значения 1, 2 и 3 соответственно. Затем мы добавляем поле $level для хранения ролей и немного изменяем конструктор так, чтобы инициализировать еще и это поле. В классе также появился еще один метод - getLevel(), который возвращает определенное сообщение в зависимости от значения поля $level. Он сравнивает это значение с каждой из классовых констант и возвращает нужную строку.

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

Явное указание типов аргументов функций

В PHP можно не задавать типы данных, так что можно не переживать о том, какие аргументы вы передаете в методы. Например, вы можете спокойно передать в функцию strlen(), считающую длину строки, численное значение. PHP сперва переведет число в строку, а затем вернет ее длину:

Echo strlen(123); // отобразит"3"

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

Например

Посмотрите на этот код:

Class Member { private $username; public function __construct($username) { $this->username = $username; } public function getUsername() { return $this->username; } } class Topic { private $member; private $subject; public function __construct($member, $subject) { $this->member = $member; $this->subject = $subject; } public function getUsername() { return $this->member->getUsername(); } } $aMember = new Member("fred"); $aTopic = new Topic($aMember, "Hello everybody!"); echo $aTopic->getUsername(); // отобразит "fred"

Данный скрипт работает так:

  • Мы создаем наш класс Member с полем $username, конструктором и методом getUsername();
  • Также создаем класс Topic для управления статьями форума. У него два поля: $member и $subject. $member - это объект класса Member, это будет автор статьи. Поле $subject - это тема статьи.
  • В классе Topic также содержится конструктор, который принимает объект класса Member и строку - тему статьи. Этими значениями он инициализирует поля класса. У него еще есть метод getUsername(), который возвращает имя участника форума. Это достигается через вызов метода getUsername() объекта Member.
  • В завершении создаем объект класса Member со значением поля username “fred”. Затем создаем объект класса Topic, передав ему Фреда и тему статьи “Hello everybody!”. В конце вызываем метод getUsername() класса Topic и отображаем на странице имя пользователя (“fred”).

Это все очень хорошо, но...

Давайте сделаем лучше!

Добавим этот фрагмент кода в конце:

Class Widget { private $colour; public function __construct($colour) { $this->colour = $colour; } public function getColour() { return $this->colour; } } $aWidget = new Widget("blue"); $anotherTopic = new Topic($aWidget, "Oops!"); // отобразит "Fatal error: Call to undefined method Widget::getUsername()" echo $anotherTopic->getUsername();

Здесь мы создаем класс Widget с полем $colour, конструктором и методом getColour(), который возвращает цвет виджета.

Затем мы создадим объект данного класса, а за ним объект Topic с аргументом $aWidget, когда на самом деле нужно передавать автора статьи, т.е. объект класса Member.

Теперь попытаемся вызвать метод getUsername() класса Topic. Этот метод обращается к методу getUsername() класса Widget. И так как в этом классе нет такого метода, мы получаем ошибку:

Fatal error: Call to undefined method Widget::getUsername()

Проблема в том, что причина ошибки не так легко уяснима. Почему объект Topic ищет метод в классе Widget, а не Member? В сложной иерархии классов будет очень сложно найти выход из такого рода ситуации.

Даем подсказку

Было бы лучше ограничить конструктор класса Topic на прием аргументов так, чтобы он мог принимать в качестве первого параметра объекты только класса Member, тем самым предостеречься от фатальных ошибок.

Это как раз то, чем занимается явное указание типов. Чтобы явно указать тип параметра, вставьте имя класса перед названием аргумента в объявлении метода:

Function myMethod(ClassName $object) { // (действия) }

Давайте подкорректируем конструктор класса Topic так, чтобы он принимал только Member:

Class Topic { private $member; private $subject; public function __construct(Member $member, $subject) { $this->member = $member; $this->subject = $subject; } public function getUsername() { return $this->member->getUsername(); } }

Теперь снова попытаемся создать объект Topic, передав ему Widget:

$aWidget = new Widget("blue"); $anotherTopic = new Topic($aWidget, "Oops!");

На этот раз PHP отобразит конкретную ошибку:

Catchable fatal error: Argument 1 passed to Topic::__construct() must be an instance of Member, instance of Widget given, called in script.php on line 55 and defined in script.php on line 24

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

Инициализация и чтение значений полей класса при помощи __get() и __set()

Как вы уже знаете, классы обычно содержат поля:

Class MyClass { public $aProperty; public $anotherProperty; }

Если поля класса - public, вы можете получить к ним доступ с помощью оператора ->:

$myObject = new MyClass; $myObject->aProperty = "hello";

Тем не менее, PHP позволяет создавать “виртуальные” поля, которых на самом деле нет в классе, но к которым можно получить доступ через оператор ->. Они могут быть полезны в таких случаях:

  • Когда у вас очень много полей, и вы хотите создать для них массив, чтобы не объявлять каждое поле отдельно;
  • Когда вам нужно хранить поле за пределами объекта, например, в другом объекте, или даже в файле или базе данных;
  • Когда вам нужно вычислять значения полей “на лету”, а не хранить их значения где-либо.

Чтобы создать такие “виртуальные” поля, нужно добавить в класс парочку волшебных методов:

  • __get($propName) вызывается автоматически при попытке прочитать значение “невидимого” поля $propName;
  • __set($propName,$propValue) вызывается автоматически при попытке задать “невидимому” полю $propName значение $propValue.

“Невидимый” в данном контексте значит, что на данном участке кода нельзя прямо получить доступ к данным полям. Например, если такого поля вообще нет в классе, или если оно существует, но оно частное, и за пределами класса нет доступа к такому полю.

Перейдем к практике. Изменим наш класс Member так, чтобы в дополнение полю $username были еще и другие случайные поля, которые будут храниться в массиве $data:

Class Member { private $username; private $data = array(); public function __get($property) { if ($property == "username") { return $this->username; } else { if (array_key_exists($property, $this->data)) { return $this->data[$property]; } else { return null; } } } public function __set($property, $value) { if ($property == "username") { $this->username = $value; } else { $this->data[$property] = $value; } } } $aMember = new Member(); $aMember->username = "fred"; $aMember->location = "San Francisco"; echo $aMember->username . "
"; // отобразит "fred" echo $aMember->location . "
"; // отобразит "San Francisco"

Вот, как это работает:

  • В классе Member есть постоянное поле private $username и private массив $data для хранения случайных “виртуальных” полей;
  • Метод __get() принимает единственный параметр $property - имя поля, значение которого нужно вернуть. Если $property = “username”, то метод вернет значение поля $username. В другом случае, метод проверит, встречается ли такой $property в ключах массива $data. Если найдется такой ключ, он вернет значение данного поля, в противном случае - null.
  • Метод __set() принимает 2 параметра: $property - имя поля, которое нужно инициализировать, и $value - значение, которое нужно задать данному полю. Если $property = “username”, метод инициализирует поле $username значением из параметра $value. В противном случае, добавляет в массив $data ключ $property со значением $value.
  • После создания класса Member, создаем объект этого класса и инициализируем его поле $username значением “fred”. Это вызывает метод __set(), который задаст значение $username объекту. Затем устанавливаем значение поля $location в “San Francisco”. Так как такого поля не существует в объекте, метод записывает его в массив $data.
  • В конце, достаем значения $username и $location и выводим их на страницу. Метод __get() достает действительное значение $username из существующего поля $username, а значение $location - из массива $data.

Как видите, с помощью методов __get() и __set() мы создали класс, в котором могут быть как настоящие поля, так и любые “виртуальные”. Из фрагмента кода, где задается значение тому или иному полю, не обязательно знать, существует ли такое поле или нет в объекте. Через обычный оператор -> можно задать полю значение или прочитать его.

В примере также показано, как можно легко создать методы, называемые “геттерами” и “сеттерами”, для доступа к частным полям. Нам не нужно создавать отдельные методы getUsername() и setUsername() для получения доступа к частному полю $username. Вместо этого мы создали методы __get() и __set() для манипулирования данным полем. Это значит, что нам нужно всего 2 метода в общем, а не по 2 метода для каждого частного поля.

На заметку: Слово о инкапсуляции. Использование частных полей класса в комбинации с геттерами и сеттерами - это лучше, чем использование переменных public. Геттеры и сеттеры дополнительно могут обрабатывать данные, задаваемые полям объекта и получаемые из них, например, проверять, в правильном ли формате находится значение, или конвертировать его в нужный формат. Геттеры и сеттеры также скрывают детали того, как имплементируются поля класса, что упрощает процесс модификации внутренней части класса, так как не нужно переписывать код, который оперирует объектами данного класса. Например, вы вдруг захотели хранить значение поля в базе данных. Если у вас уже были геттеры и сеттеры, все, что вам нужно, - это переписать их. А вызывающий код останется таким же. Эта техника называется инкапсуляцией, и это одно из главных преимуществ ООП.

Перегрузка методов с помощью __call()

Геттеры и сеттеры используются для запрета на доступ к частным переменным. В этом же направлении используется метод __call() для запрета доступа к частным методам. Как только из кода вызывается метод класса, который либо не существует, либо он недоступен, автоматически вызывается метод __call(). Вот общий синтаксис метода:

Public function __call($methodName, $arguments) { // (действия) }

Когда производится попытка вызвать недоступный метод класса, PHP автоматически вызывает метод __call(), в который передает строку - имя вызываемого метода и список передаваемых параметров в массиве. Затем ваш метод __call() должен будет определенным способом обработать вызов и, в случае необходимости, вернуть значения.

Метод __call() полезен в ситуациях, когда вам нужно передать некую функциональность класса другому классу. Вот простой пример:

Class Member { private $username; public function __construct($username) { $this->username = $username; } public function getUsername() { return $this->username; } } class Topic { private $member; private $subject; public function __construct($member, $subject) { $this->member = $member; $this->subject = $subject; } public function getSubject() { return $this->subject; } public function __call($method, $arguments) { return $this->member->$method($arguments); } } $aMember = new Member("fred"); $aTopic = new Topic($aMember, "Hello everybody!"); echo $aTopic->getSubject() . "
"; // отобразит "Hello everybody!" echo $aTopic->getUsername() . "
"; // отобразит "fred"

Данный пример похож на тот, что приводился в разделе о явном указании типов. У нас есть класс Member с полем $username и класс Topic с полем - объектом класса Member (автор статьи) и полем $subject - темой статьи. Класс Topic содержит метод getSubject() для получения темы статьи, но в нем нет метода, который возвращал бы имя автора статьи. Вместо него в нем есть метод __call(), который вызывает несуществующий метод и передает аргументы методу класса Member.

Когда в коде вызывается метод $aTopic->getUsername(), PHP понимает, что такого метода в классе Topic не существует. Поэтому вызывается метод __call(), который в свою очередь, вызывает метод getUsername() класса Member. Этот метод возвращает имя автора методу __call(), а тот отправляет полученное значение вызывающему коду.

На заметку: в PHP есть и другие методы, касающиеся перегрузки, например, __isset(), __unset(), и __callStatic().

Заключение

В этом уроке вы углубили свои знания по ООП в PHP, рассмотрев более детально поля и методы. Вы изучили:

  • Конструкторы и деструкторы, полезные для инициализации полей и очистки памяти после удаления объектов;
  • Статические поля и методы, которые работают на уровне класса, а не на уровне объекта;
  • Классовые константы, полезные для хранения фиксированных значений, необходимых на уровне класса;
  • Явное указание типов, с помощью которого можно лимитировать типы аргументов, передаваемых в метод;
  • Волшебные методы __get(), __set() и __call(), которые служат для получения доступа к частным полям и методам класса. Реализация этих методов позволяет вам создавать “виртуальные” поля и методы, которые не существуют в классе, но в то же время, могут быть вызваны.

Со знаниями, полученными в этом и предыдущем уроках, вы можете начать писать на ООП. Но на этом все только начинается! В следующем уроке мы поговорим о силе ООП - способности классов наследовать функциональность от других классов.

Удачного программирования!

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

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

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

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

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

Когда родословная начинается значимым классом, который имеет собственные данные и свойства, связанные с внешними данными (объектами), без function construct PHP не обойтись.

В данном примере при создании (PHP construct) экземпляра класса даты будет вызвана данная функция (конструктор). Она имеет специфическое имя __construct и вызывается автоматом только один раз, когда создается экземпляр класса.

В этом классе предусмотрена статическая переменная $iUniqueNo, которая в каждом новом экземпляре такого класса будет иметь уникальное значение. Экземпляры классов не имеют между собой ничего общего кроме описания в рамках синтаксиса PHP и предусмотренного разработчиком взаимодействия их методов.

Наследование логики инициализации

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

В этом примере ключевое слово parent:: позволяет вызвать конструктор предка с уровня потомка. Семантика проста. Сначала должен выполнить свою инициализацию предок, затем текущий экземпляр. Первый следует своей логике, второй - своей.

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

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

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

Общие и частные конструкторы

По умолчанию конструктор является общим и доступен для использования всеми потомками. Не обязательно указывать public function construct, PHP по умолчанию рассматривает все описанное, как общее.

Насколько имеет смысл использовать ключевое слово private при описании конструкторов - специфика задачи, особенность процесса разработки или предпочтений программиста?

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

Время жизни объекта

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

Участие AJAX через JavaScript браузера и надлежащий код на сервере поможет продлить жизнь объектов, но в любом варианте это будет ограниченный стиль.

PHP предоставляет возможность исполнять скрипт на сервере, когда клиент "отключился", и "допускать" клиента обратно в скрипт, запущенный им ранее, но это вовсе не тот вариант, когда объектно-ориентированная программа реализована на языке С++.

В последнем случае можно построить полноценную систему объектов, которая будет существовать "вечно", пока программа запущена и работает. Впрочем, это единственное чем может похвастаться стационарный язык программирования вроде С++, С#, Pascal&Delphi. В динамичном интернет-мире все строится иначе, живет быстрее и достигает большего.

От сериализации к самосохранению

Можно найти историческое обоснование термину "сериализация" и появлению в обиходе понятия "магические методы". Но все гораздо проще. Ровно тем же, чем отличается свобода С++ от жесткости С#, сериализация отличается от банальных понятий:

  • записать объект в строку;
  • прочитать объект из строки.

Окружить сказанное магией и мифическими магическими методами - красиво, звучно, но мало практично. Мир информации интересен прежде всего тем, что все видимое, слышимое и осязаемое можно описать простым и последовательным текстом.

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

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

От самосохранения к саморазвитию

Семантика конструктора PHP construct в пределах его синтаксиса ограничена, но если конструктор разрабатывать с развиваемой позиции:

  • есть начало - создается абсолютно новый экземпляр;
  • есть текущее состояние - создается уже существующий экземпляр.

Ограничения PHP ввиду того, что система объектов на нем существует только в момент формирования страницы, снимутся сами собой.

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

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

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

В прошлой статье посвященной объектно-ориентированному программированию в PHP мы рассмотрели основы ООП в PHP , а именно изучили понятия классов, объектов, свойств и методов, - и мы научились создавать простые классы и объекты в PHP .

В сегодняшней статье мы поговорим об конструкторах и деструкторах в ООП .

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

Очень часто при создании экземпляра объекта на основе класса требуется выполнить какие-то базовые настройки, будь то установка свойств объекта или же открытие файлов конфигурации, да мало ли что еще. Именно для этих целей в ООП и существует метод конструктор. В версиях до PHP 5 имя метода конструктора совпадало с именем класса к которому он относится, а начиная с версии PHP 5 имя метода конструктора необходимо называть __construct() (это 2 подчеркивания перед словом construct()).

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

Class MyClass { public function __construct() { echo "Я только что был создан!"; } } $myObject = new MyClass(); // выведет «Я только что был создан!»

MyClass содержит простой конструктор, который выводит сообщения с помощью оператора echo. В последней сточке кода мы создаем экземпляр класса MyClass() и когда это происходит PHP автоматически вызывает конструктор и на экран выводится сообщение «Я только что был создан!»

Давайте теперь посмотрим еще на один пример, в котором при создании объекта происходит инициализации его свойств:

Class Product{ private $title; private $price; private $discount; public function __construct($title, $price, $discount) { $this->title = $title; $this->price = $price; $this->discount = $discount; } public function getProduct(){ echo "Наименование товара: "".$this->title.""
"; echo "Цена товара: ".$this->price ." $
"; echo "Скидка: ".$this->discount ."%
"; } } $product = new Product("Мастер создания форм",20,25); $product->getProduct();

Если запустить этот скрипт , то на экран будет выведена следующая информация:

Наименование товара: "Мастер создания форм" Цена товара: 20$ Скидка: 25%

Класс Product содержит 3 приватных (private) свойства: $title , $price и $discount , и конструктор, который принимает 3 аргумента: название товара, цена товара и скидка. Также имеется метод getProduct() , который выводит на экран информацию о товаре. В методе конструктора используется псевдопеременная $this для присвоения значений соответствующим свойствам объекта.

Далее мы создаем новый объект Product и передаем в нем информацию о товаре. Конструктор принимает эти данные и устанавливает соответствующие свойства объекта. И наконец вызывается метод getProduct(), который выводит на экран сохраненные значения свойств объекта.

Деструкторы

Подобно конструкторам в PHP существуют деструкторы, которые вызываются строго перед тем, как объект удаляется из памяти.

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

Примечание: PHP автоматически удаляет объект из памяти, когда не остается ни одной переменной, указывающей на него. Например, если вы создадите новый объект и сохраните его в переменной $myObject, а затем удалите ее с помощью метода unset($myObject) , то сам объект также удалится. Также, если вы создали локальную переменную в какой-либо функции, она (вместе с объектом) удалится, когда функция завершит работу.

В отличии от конструкторов, в деструкторы нельзя передавать никакие параметры!

Чтобы создать деструктор, добавьте в класс метод __destruct() . Вот простой пример:

Class MyClass { public function __destruct() { echo "Я деструктор. Объект был удален. Пока!"; } } $myObject = new MyClass(); unset($myObject); // отобразит "Я деструктор. Объект был удален. Пока!"

Мы создали простенький деструктор, который отображает на странице сообщение. Затем мы создали объект нашего класса и сразу же удалили его, вызвав метод unset() для переменной, которая ссылается на объект. Перед самым удалением объекта вызвался деструктор, который отобразил в браузере сообщение "Я деструктор. Объект был удален. Пока!" .

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

Class MyClass { public function __destruct() { echo "Я деструктор. Объект был удален. Пока!"; } } $myObject = new MyClass(); exit; // отобразит "Я деструктор. Объект был удален. Пока!"

Если работа скрипта прекратится из-за возникшей ошибки, деструктор тоже вызовется.

Примечание: при создании объектов класса-наследника, конструкторы класса-родителя не вызываются автоматически. Вызывается только конструктор самого наследника. Тем не менее вы можете вызвать конструктор родителя из класса-наследника таким образом: parent::__construct() . То же самое касается деструкторов. Вызвать деструктор родителя можно так: parent:__destruct() .

О на следовании мы поговорим в следующих статьях. А на сегодня все!