PHP

Объектно-ориентированный PHP: автоматическая загрузка классов, сериализация и получение информации об объектах

Добро пожаловать в четвертый урок из серии, посвященной ООП в 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()
Паттерны: техника, позволяющая достичь много целей при помощи объектов;
Копирование и сравнение объектов;
Статическое позднее связывание, которое дает кучу возможностей при вызове статических наследуемых методов.

Если у меня будет время, и если у вас будет такая необходимость, я обязательно расскажу о них в следующих уроках.

Удачного кодирования!
Добавлено: 19 Августа 2013 06:16:05 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...