Добро пожаловать в четвертый урок из серии, посвященной ООП в PHP! Если вы пропустили первые три урока, вам возможно, захочется рассмотреть и их, дабы узнать подробнее о классах и объектах в PHP:
Объектно-ориентированный PHP: специально для начинающих
Объектно-ориентированный PHP: подробнее о методах и полях класса
Объектно-ориентированный PHP: работа с наследованием
Если вы прошли все три предыдущих урока, то вы уже знакомы с основными концепциями ООП в PHP: классами, объектами, методами, полями и наследованием. В этом, последнем, уроке я постараюсь завершить тему объектно-ориентированного программирования в PHP, а также познакомить вас с некоторыми чрезвычайно полезными свойствами PHP, связанных с ООП:
Как автоматически загружать классы;
Как конвертировать объекты в строку и строку в объекты для удобства хранения и передачи;
Как получить подробную информацию об определенном классе или объекте.
Готовы? Вперед!
Автоматическая загрузка классов
Как правило, полезно хранить каждый PHP класс в отдельном файле. Например, в приложении веб-форума, вы бы хранили класс Member в файле Member.php, а класс Topic - в файле Topic.php. Скорее всего, вы будете хранить все эти файлы в папке classes где-то на сайте:
classes/
Member.php
Topic.php
Затем, если понадобится создать объект класса, например, Member, PHP сначала придется подключить файл с этим классом:
<?php
require_once( "classes/Member.php" );
$member = new Member();
?>
Хранение классов таким образом - вещь полезная не только для вашей психики, но и для использования такого удобного свойства, как автоматическая загрузка (autoloading).
Автоматическая загрузка работает так. Где-то в начале приложения PHP вы создаете специальную функцию __autoload(). В последствии, если где-то в коде будет попытка создать объект класса, о котором ничего не известно, PHP автоматически вызовет данную функцию, передав ей в качестве параметра имя искомого класса. Вся работа функции заключается в том, чтобы найти нужный файл и подгрузить его к скрипту, тем самым загрузить сам класс. После этого PHP уже сможет создать объект данного класса.
Давайте приведем пример. Напишем функцию __autoload() для автоматической загрузки классов из папки classes:
<?php
function __autoload( $className ) {
$className = str_replace( "..", "", $className );
require_once( "classes/$className.php" );
echo "Loaded classes/$className.php<br>";
}
$member = new Member();
echo "Created object: ";
print_r( $member );
?>
Вот, как это работает. Сперва, создаем функцию __autoload() с входным параметром $className. В начале, функция убирает все подстроки “..” из полученного параметра, это делается в целях безопасности. Затем, с помощью функции require_once(), она подгружает нужный файл. Функции известно, что он находится в папке classes, и его расширение - .php. Функция также выводит на страницу сообщение, так что мы сразу увидим, что она отработала.
Затем протестим нашу функцию, создав объект класса Member. Так как мы предварительно не подгрузили файл с данным классом, PHP запустит функцию __autoload(), передав ей имя класса - “Member”. Она, в свою очередь, будет искать файл classes/Member.php. Затем PHP создает-таки объект Member. В завершении, отображаем сообщение о том, что объект создан.
Чтобы протестировать работу данного скрипта, создадим папку classes в том же каталоге, что и скрипт. Создадим в ней файл Member.php с простеньким классом:
<?php
class Member {
}
?>
Теперь, когда мы запустим скрипт, загрузится файл classes/Member.php, создастся объект класса Member, и на экране отобразится следующее:
Loaded classes/Member.php
Created object: Member Object ( )
Автоматическая загрузка поможет вам сэкономить уйму времени, тем более, если у вас огромное количество классов. Вместо того, чтобы вызывать функцию require_once() в начале каждого файла, вы просто создаете в начале всего приложения функцию __autoload(), и отдаете управление загрузкой классов в руки PHP!
Сериализация объектов
Следующее свойство ООП, о котором я расскажу, - это конвертация объекта в строку и, наоборот, строки в объект. Это может быть полезно в случаях, когда вам нужно передавать объекты между скриптами и даже приложениями. Вот конкретные случаи:
Передача объектов через поля веб-форм;
Передача объектов через адресную строку;
Хранение объекта в текстовом файле или одном поле таблицы базы данных.
Для конвертации объекта в строку, и обратно, используются следующие функции:
serialize() - принимает объект и возвращает строковое представление его класса и свойств;
unserialize() - принимает строку, созданную при помощи serialize(), и возвращает объект.
Давайте посмотрим на эти две функции в деле:
<?php
class Member
{
public $username = "";
private $loggedIn = false;
public function login() {
$this->loggedIn = true;
}
public function logout() {
$this->loggedIn = false;
}
public function isLoggedIn() {
return $this->loggedIn;
}
}
$member = new Member();
$member->username = "Fred";
$member->login();
$memberString = serialize( $member );
echo "Converted the Member object to a string: '$memberString'<br>";
echo "Converting the string back to an object...<br>";
$member2 = unserialize( $memberString );
echo $member2->username . " is " . ( $member2->isLoggedIn() ? "logged in" : "logged out" ) . "<br>";
?>
Мы создали простенький класс Member с полем public $username, полем private $loggedIn и тремя методами public: login(), logout() и isLoggedIn(). Затем наш скрипт создает объект класса Member, дает ему имя "Fred" и логинит его.
Затем вызываем функцию serialize(), передав ей объект класса Member. serialize() возвращает строковое представление данного объекта, которое мы сохраним в переменной $memberString и отобразим на странице:
Converted the Member object to a string:
'O:6:"Member":2:{s:8:"username";s:4:"Fred";s:16:"MemberloggedIn";b:1;}'
Затем конвертируем нашу строку обратно в объект класса Member, вызвав функцию unserialize(), и сохраняем полученный объект в переменной $member2. Чтобы проверить, что наш объект сконвертировался верно и полностью, мы отображаем значение его поля $username и вызываем его метод isLoggedIn(), чтобы проверить, залогинился ли пользователь. Вот, что отобразится на странице:
Converting the string back to an object...
Fred is logged in
Как видите, строка, созданная функцией serialize(), содержит имя класса, а также названия всех его полей и их значения для конкретного объекта. (Перед полями private записывается имя класса, без пробелов.) Тем не менее, названия методов класса не записываются в строку.
Чтобы сработала функция unserialize(), класс объекта, который должен быть сконвертирован из строки, должен подгружаться еще до того, как идет вызов unserialize(). Вы можете написать сам класс в том же скрипте, где вызывается unserialize(), или подгрузить файл с данным классом через функцию require_once(). Также вы можете создать функцию __autoload(), о которой мы говорили ранее. PHP вызовет __autoload(), если не сможет найти класс, объект которого вы пытаетесь сконвертировать.
На заметку: функции serialize() и unserialize() работают также и с другими типами данных, таких как массивы. Тем не менее, они не работают с ресурсами.
Функции __sleep() и __wakeup()
Иногда, перед сериализацией объекта, возникает необходимость кое-что почистить. Например, может понадобиться записать объект в базу данных и закрыть соединение. Подобно этому, после десериализации, вы захотите восстановить соединение и произвести еще какие-то действия.
В PHP есть несколько специальных методов, которые помогут вам в этом:
__sleep() вызывается строго перед тем, как объект сериализуется с помощью функции serialize().
__wakeup() вызывается сразу после того, как объект десериализуется с помощью unserialize().
Ваша функция __sleep() должна будет возвращать список полей класса. Именно тех полей, которые функция serialize() включит в возвращаемую строку. Вы можете использовать это для того, чтобы исключить ненужные поля из строкового представления объекта.
Давайте добавим в наш предыдущий пример методы __sleep() и __wakeup():
<?php
class Member
{
public $username = "";
private $loggedIn = false;
public function login() {
$this->loggedIn = true;
}
public function logout() {
$this->loggedIn = false;
}
public function isLoggedIn() {
return $this->loggedIn;
}
public function __sleep() {
echo "Cleaning up the object...<br>";
return array( "username" );
}
public function __wakeup() {
echo "Setting up the object...<br>";
}
}
$member = new Member();
$member->username = "Fred";
$member->login();
$memberString = serialize( $member );
echo "Converted the Member object to a string: '$memberString'<br>";
echo "Converting the string back to an object...<br>";
$member2 = unserialize( $memberString );
echo $member2->username . " is " . ( $member2->isLoggedIn() ? "logged in" : "logged out" ) . "<br>";
?>
Вот, что отобразится на странице:
Cleaning up the object...
Converted the Member object to a string: 'O:6:"Member":1:{s:8:"username";s:4:"Fred";}'
Converting the string back to an object...
Setting up the object...
Fred is logged out
Обратите внимание на то, что:
Наши методы __sleep() и __wakeup() в действительности ничего не подчищают и не задают; вместо этого, они просто выводят сообщения "Cleaning up the object..." и "Setting up the object...".
Так как мы включаем только поле $username в массив, который возвращает __sleep(), в результирующей строке не будет присутствовать поле $loggedIn.
В результате, поле $loggedIn десериализованного объекта примет значение по умолчанию - false, поэтому при вызове метода isLoggedIn() от десериализованного объекта, он вернет false. Вот, почему скрипт отобразит сообщение "Fred is logged out".
Если вы хотите написать метод __sleep() и чтобы все поля были сериализованы, тогда вам понадобится перечислять все поля для массива, который возвращает метод __sleep(). Этого легко достичь при помощи функций PHP array_keys() и get_object_vars() таким образом:
public function __sleep() {
// почистить
return array_keys( get_object_vars( $this ) );
}
На заметку: другой классный способ сериализации объектов (причем, кросс-платформенный) - это конвертирование строк JSON.
Получение информации о классах и объектах
В PHP есть много полезных функций для получения информации о классах и объектах. Это полезно в случаях, когда нужно работать с объектами из другого приложения, а также поможет вам написать удобный, гибкий код. Используя эти функции для объекта, вы можете получить имя его класса, проверить, создан ли он от определенного класса, получить его поля и методы и др.
Вот краткий перечень наиболее часто употребляемых из них:
Функция Описание
get_class( $obj ) Возвращает имя класса, которому принадлежит объект $obj
get_parent_class($className или $obj ) Возвращает имя класса, который является родителем класса $className, или родителем класса, чьим объектом является $obj
is_a( $obj, $className ) Возвращает true, если $obj - объект класса $className, или если $obj - объект класса - потомка $className
$obj instanceof $className Делает то же самое, что и функция is_a()
get_class_methods($className или $obj ) Возвращает массив названий всех методов класса $className или объекта $obj
get_class_vars( $className ) Возвращает ассоциативный массив всех полей класса $className вместе с их значениями по умолчанию
get_object_vars( $obj ) Возвращает ассоциативный массив всех полей объекта $obj, вместе с их текущими значениями
Важно отметить, что такие функции, как get_class_methods(), get_class_vars() и get_object_vars(), возвращают только те методы и поля, которые находятся в одной области действия с кодом, их вызывающим. Например, метод private будет включен в возвращаемое значение функции get_class_methods(), только если get_class_methods() вызывается внутри метода класса.
Давайте поэкспериментируем с некоторыми из этих функций на таком примере:
<?php
class Member {
public $username = "";
private $loggedIn = false;
public function login() {
$this->loggedIn = true;
}
public function logout() {
$this->loggedIn = false;
}
public function isLoggedIn() {
return $this->loggedIn;
}
public function listMyProperties() {
echo "My properties are: ";
print_r( get_object_vars( $this ) );
}
}
class Administrator extends Member {
public $adminLevel = 1;
public function createForum( $forumName ) {
echo "$this->username created a new forum: $forumName<br>";
}
public function banMember( $member ) {
echo "$this->username banned the member: $member->username<br>";
}
}
$admin = new Administrator();
$admin->username = "Mary";
$admin->login();
echo "1. $admin->username's class is " . get_class( $admin ) . "<br>";
echo "2. $admin->username's parent class is " . get_parent_class( $admin ) . "<br>";
echo "3. Is $admin->username a Member? " . ( is_a( $admin, "Member" ) ? "Yes" : "No" ) . "<br>";
echo "4. $admin->username's methods are: ";
print_r( get_class_methods( $admin ) );
echo "<br>5. $admin->username's class properties are: ";
print_r( get_class_vars( get_class( $admin ) ) );
echo "<br>6. $admin->username's object properties are: ";
print_r( get_object_vars( $admin ) );
echo "<br>7. ";
$admin->listMyProperties();
?>
В скрипте создается класс Member с несколькими полями и методами. Также в нем создается класс Administrator - дочерний от класса Member. В классе Administrator добавляется поле public $adminLevel, а также пара методов: createForum() и banMember(), которые будут только у объектов класса Administrator.
После создания класса, мы создаем объект класса Administrator, сохраняем его в переменной $admin, даем ему имя "Mary" и логиним админа при помощи вызова метода login().
В последней части скрипта (строки 44-54), мы применяем различные функции к нашим объектам и классам. Вот результат работы данного кода:
Mary's class is Administrator
Mary's parent class is Member
Is Mary a Member? Yes
Mary's methods are: Array ( [0] => createForum [1] => banMember [2] => login [3] => logout [4] => isLoggedIn [5] => listMyProperties )
Mary's class properties are: Array ( [adminLevel] => 1 [username] => )
Mary's object properties are: Array ( [adminLevel] => 1 [username] => Mary )
My properties are: Array ( [adminLevel] => 1 [username] => Mary [loggedIn] => 1 )
Вот, как отработают данные функции:
get_class( $admin ) возвращает строковое значение "Administrator".
get_parent_class( $admin ) возвращает строковое значение "Member".
is_a( $admin, "Member" ) возвращает true.
get_class_methods( $admin ) возвращает массив названий методов для классов Administrator и Member. Так как все эти методы - public, они все будут записаны в массив.
get_class_vars( $admin ) возвращает массив названий полей public и соответствующих им значений по умолчанию для классов Administrator и Member.
get_object_vars( $admin ) возвращает массив названий полей public и соответствующих им текущих значений.
В конце вызываем метод класса Member - listMyProperties(). Этот метод вызывает функцию get_object_vars() и отображает результат. Тем не менее, так как get_object_vars() теперь вызывается изнутри класса, он также возвращает поле private - $loggedIn.
Как видите, в PHP очень легко извлечь любую информацию об объекте или классе. В PHP есть еще функции для получения инфы об объекте, классе и интерфейсе, например, class_exists(), get_called_class(), get_declared_classes(), get_declared_interfaces(), interface_exists(), is_subclass_of(), method_exists() и property_exists().
Итоги
В этом уроке вы ознакомились с тремя полезнейшими свойствами PHP объектов:
Использование функции __autoload() для автоматической загрузки файлов с необходимыми классами;
Как конвертировать объекты в строки и обратно при помощи функций PHP serialize() и unserialize();
Получение информации об объектах, классах, предках при помощи PHP функций.
С помощью этих трех свойств работать с классами и объектами в PHP не составит огромного труда.
Надеюсь, вам понравилась данная серия уроков, посвященная ООП в PHP! Если вы прошли все 4 урока, у вас достаточно знаний для написания классных PHP сайтов и приложений. Однако, на этом история не заканчивается — в PHP еще много полезных свойств, предназначенных для работы с ООП, например:
Итерация: как последовательно пройтись по всем полям объекта;
Больше специальных методов, включая __callStatic(), __isset(), __unset(), __toString(), __invoke(), __set_state() и __clone()
Паттерны: техника, позволяющая достичь много целей при помощи объектов;
Копирование и сравнение объектов;
Статическое позднее связывание, которое дает кучу возможностей при вызове статических наследуемых методов.
Если у меня будет время, и если у вас будет такая необходимость, я обязательно расскажу о них в следующих уроках.
Удачного кодирования!