objects07 16

Рефлексия

Программный интерфейс Reflection API для PHP версии 5 — то же самое, что и API-интерфейс System.Reflection сборки mscorlib.dll для C#. Он состоит из встроенных классов для анализа свойств, методов и классов. В некоторых отношениях он напоминает рассмотренные в предыдущей статье функции для работы с объектами, такие как get_class_vars(), но является более гибким и позволяет учитывать больше нюансов. Он также предназначен для более эффективной работы с объектно-ориентированными средствами PHP, такими как управление доступом, интерфейсы и абстрактные классы. Старые, более ограниченные, функции классов так работать не могут.

Для проработки последующих примеров вам понадобиться структура класса ShopProduct, которую мы определили к данному моменту и вспомогательные классы - исходный код.

Основные сведения

Интерфейс Reflection API можно использовать для исследования не только классов. Например, класс ReflectionFunction предоставляет информацию о заданной функции, a ReflectionExtension — информацию о скомпилированных расширениях языка PHP. В таблице ниже перечислены некоторые классы интерфейса Reflection API:

Некоторые классы интерфейса Reflection API
Класс Описание
Reflection Содержит статический метод export(), предоставляющий итоговую информацию о классе
ReflectionClass Позволяет получить информацию о классе и содержит средства для работы с ним
ReflectionMethod Позволяет получить информацию о методах класса и содержит средства для работы с ними
ReflectionParameter Позволяет получить информацию об аргументах метода
ReflectionProperty Позволяет получить информацию о свойствах класса
ReflectionFunction Позволяет получить информацию о функциях и содержит средства для работы с ними
ReflectionExtension Позволяет получить информацию о расширениях PHP
ReflectionException Предназначен для обработки ошибок

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

Мы уже встречались с некоторыми функциями для изучения атрибутов классов. Они полезны, но обычно довольно ограниченны. А теперь давайте рассмотрим инструмент, который готов к выполнению данной работы. Класс ReflectionClass предоставляет методы, которые собирают информацию обо всех аспектах заданного класса, независимо от того, внутренний это класс или определенный пользователем. Конструктору класса ReflectionClass в качестве единственного аргумента передается имя класса, как показано ниже:

Код PHP
$class_info = new ReflectionClass('CDProduct');
echo '<pre>';
Reflection::export($class_info);
echo '</pre>';

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

Исчерпывающие сведения о классе, полученные с помощью рефлексии

Как видите, метод Reflection::export() предоставляет отличный доступ к информации о классе. Этот метод дает обобщенную информацию почти о всех аспектах класса CDProduct, включая состояние доступа к свойствам и методам, аргументы, необходимые каждому методу, реализованные интерфейсы и расположение каждого метода внутри файла сценария.

Исследование класса

Метод Reflection::export() предоставляет много полезной информации для отладки, но интерфейс Reflection API можно использовать особым образом. Давайте будем работать непосредственно с классами Reflection. Вы уже знаете, как создать экземпляр объекта ReflectionClass. А теперь давайте используем объект ReflectionClass, чтобы исследовать класс CDProduct в процессе выполнения сценария. Мы хотим узнать, к какому типу класса он относится и можно ли создать его экземпляр? Вот функция, которая поможет ответить на эти вопросы:

Код PHP
function classData( ReflectionClass $class ) 
{
	$details = "";
	$name = $class->getName();
	
	if ( $class->isUserDefined() ) {
		$details .= "$name -- класс определен пользователем<br>";
	}
	
	if ( $class->isInternal() ) {
		$details .= "$name -- встроенный класс<br>";
	}
	
	if ( $class->isInterface() ) {
		$details .= "$name -- это интерфейс<br>";
	}
	
	if ( $class->isAbstract() ) {
		$details .= "$name -- это абстрактный класс<br>";
	}
	
	if ( $class->isFinal() ) {
		$details .= "$name -- это завершенный класс (не поддерживает наследования)<br>";
	}
	
	if ( $class->isInstantiable() ) {
		$details .= "$name -- можно создать экземпляр этого класса<br>";
	} else {
		$details .= "$name -- нельзя создать экземпляр этого класса<br>";
	}
	return $details;
}

$class_info = new ReflectionClass('CDProduct');
echo classData($class_info);

Мы создаем объект типа ReflectionClass и присваиваем его переменной $class_info. При этом конструктору класса ReflectionClass передается строка, содержащая имя исследуемого класса 'CDProduct'. Переменная $class_info затем передается функции classData(), демонстрирующей некоторые методы, которые можно использовать для запросов к классу. Эти методы, вероятно, не требуют объяснений, но мы все-таки приведем их краткие описания.

Метод ReflectionClass::getName() возвращает имя исследуемого класса. Метод ReflectionClass::isUserDefined() возвращает истинное значение, если класс был объявлен в PHP-коде. Метод ReflectionClass::isInternal() возвращает истинное значение, если класс является встроенным. С помощью метода ReflectionClass::isAbstract() можно проверить, является ли класс абстрактным. Метод ReflectionClass::isInterface() позволяет узнать, является ли класс интерфейсом.

Определение параметров класса с помощью рефлексии

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

Код PHP
class ReflectionUtil {
  static function getClassSource( ReflectionClass $class ) {
    $path = $class->getFileName();
    $lines = @file( $path );
    $from = $class->getStartLine();
    $to   = $class->getEndLine();
    $len  = $to - $from + 1;
    return implode( array_slice( $lines, $from-1, $len ));
  }
}

echo '<pre>'.ReflectionUtil::getClassSource(
  new ReflectionClass( 'CdProduct' )).'</pre>';

Как видите, класс ReflectionUtil очень прост и содержит единственный статический метод ReflectionUtil::getClassSource(). Этому методу в качестве единственного аргумента передается объект типа ReflectionClass, а он возвращает исходный код класса. Метод ReflectionClass::getFileName() возвращает абсолютное имя файла класса, поэтому код должен работать без проблем и открыть данный исходный файл. Функция file() возвращает массив всех строк в файле. Метод ReflectionClass::getStartLine() возвращает номер начальной строки в исходном файле, а метод ReflectionClass::getEndLine() — номер последней строки, в которых содержится определение класса. И теперь для получения нужных строк достаточно воспользоваться функцией array_slice().

Для компактности в этом коде опущена обработка ошибок. Но в реальном приложении нужно будет проверять аргументы и коды возврата.

Определение исходного кода класса с помощью рефлексии

Исследование методов

Так же как ReflectionClass используется для исследования класса, объект типа ReflectionMethod применяется для исследования метода. Ссылку на объект ReflectionMethod можно получить двумя способами. Во-первых, можно получить массив объектов типа ReflectionMethod, вызвав метод ReflectionClass::getMethods(). А во-вторых, если вас интересует конкретный метод, то передайте его имя методу ReflectionClass::getMethod(), который и вернет соответствующий объект типа ReflectionMethod.

Ниже мы воспользуемся методом ReflectionClass::getMethods(), чтобы проверить возможности класса ReflectionMethod:

Код PHP
function methodData( ReflectionMethod $method ) {
	$details = "";
	$name = $method->getName();
	
	if  ($method->isUserDefined()) {
		$details .= "$name -- метод определен пользователем<br>";
	}
	if  ($method->isInternal()) {
		$details .= "$name -- внутренний метод<br>";
	}
	if  ($method->isAbstract()) {
		$details .= "$name -- абстрактный метод<br>";
	}
	if  ($method->isPublic()) {
		$details .= "$name -- общедоступный метод<br>";
	}
	if  ($method->isProtected()) {
		$details .= "$name -- защищенный метод<br>";
	}
	if  ($method->isPrivate()) {
		$details .= "$name -- закрытый метод<br>";
	}
	if  ($method->isStatic()) {
		$details .= "$name -- статический метод<br>";
	}
	if  ($method->isFinal()) {
		$details .= "$name -- завершенный метод<br>";
	}
	if  ($method->isConstructor()) {
		$details .= "$name -- метод-конструктор<br>";
	}
	if  ($method->returnsReference()) {
		$details .= "$name метод возвращает ссылку, а не значение<br>";
	}
	return $details;
}

$class_info = new ReflectionClass('CDProduct');
$methods = $class_info->getMethods();

foreach ($methods as $method) {
	print methodData($method);
	print "<br>----<br>";
}

В этом коде для получения массива объектов типа ReflectionMethod используется вызов метода ReflectionClass::getMethods(). Затем в цикле выполняется обращение к каждому элементу массива и вызывается функция methodData(), которой передается полученный объект типа ReflectionMethod.

Исследование методов класса с помощью рефлексии

Имена методов, вызываемых в функции methodData(), отражают их назначение: код проверяет, является ли метод определенным пользователем, встроенным, абстрактным, общедоступным, защищенным, статическим или завершенным. Можно также проверить, является ли метод конструктором для своего класса и возвращает он ссылку или значение. Однако здесь нужно сделать одно замечание: метод ReflectionMethod::returnsReference() не возвращает истинное значение, даже если проверяемый метод возвращает объект целиком (а не ссылку на него), хотя в PHP 5 объекты передаются и присваиваются по ссылке. Этот метод возвращает истинное значение, только если исследуемый метод был явно объявлен таким, который возвращает ссылку (путем помещения символа амперсанда перед именем метода).

Исследование аргументов методов

Теперь, когда стало возможным ограничивать типы аргументов с помощью сигнатур методов, чрезвычайно полезной кажется возможность исследования аргументов, объявленных в сигнатуре метода. В интерфейсе Reflection API именно для этой цели предусмотрен класс ReflectionParameter. Чтобы получить объект типа ReflectionParameter, нам понадобится помощь объекта ReflectionMethod. Метод ReflectionMethod::getParameters() возвращает массив объектов типа ReflectionParameter.

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

Ниже приведен пример использования некоторых методов класса ReflectionParameter:

Код PHP
function argData( ReflectionParameter $arg ) {
	$details = "";
	$declaringclass = $arg->getDeclaringClass();
	$name	= $arg->getName();
	$class = $arg->getClass();
	$position = $arg->getPosition();
	$details .= "\$$name находится в позиции $position<br>";
	if ( ! empty( $class )	) {
		$classname = $class->getName();
		$details .= "\$$name должен быть объектом типа $classname<br>";
	}
	
	if ( $arg->isPassedByReference() ) {
		$details .= "\$$name передан по ссылке<br>";
	}

	if ( $arg->isDefaultValueAvailable()	) {
		$def = $arg->getDefaultValue();
		$details .= "\$$name по умолчанию равно: $def<br>";
	}

	if ( $arg->allowsNull()	) { 
		$details .= "\$$name может быть null<br>";
	}

	return $details;
}

$class_info = new ReflectionClass( 'CDProduct' );
$method = $class_info->getMethod( "__construct" );
$params = $method->getParameters();

foreach ( $params as $param ) {
    print argData( $param )."<br>";
}
Получение информации о аргументах метода-конструктора

Метод ReflectionClass::getMethod() возвращает объект типа ReflectionMethod для интересующего нас метода (в рассмотренном примере это конструктор). Затем вызывается метод ReflectionClass::getParameters() для получения массива объектов типа ReflectionParameter, соответствующих данному методу. В цикле функции argData() передается объект типа ReflectionParameter, а она возвращает информацию об аргументе.

Сначала мы узнаем имя переменной-аргумента с помощью метода ReflectionParameter::getName(). Метод ReflectionParameter::getClass() возвращает объект типа ReflectionClass, если в сигнатуре метода было использовано уточнение. Затем с помощью метода isPassedByReference() проверяется, является ли аргумент ссылкой. И, наконец, с помощью метода isDefaultValueAvailable() проверяется, было ли аргументу присвоено значение по умолчанию.

Функции для работы с классами и объектами
Методы проектирования

Комментарии (0)

Результаты поиска по запросу

Система Orphus