04.12.2016   2826

PHP Reflection API

PHP Reflection API: учимся применять

Всем привет. Поговорим для начала о том, что же вообще такое рефлексия (от англ. reflection - отражение) в программировании. Этот термин пришёл к нам из психологии. Там данное слово означает способность человека к самоанализу, взгляду на собственно самого себя, оценке своих поступков, мыслей и прочего вот этого всего. Алсо, человек в процессе всего этого может ещё и изменять свою точку зрения, что приведёт к изменению его поведения. Конечно, наверняка есть более подходящее понятие, но для нас главное понять суть.

Попробуем теперь перенести это понятие из жизни человека на время выполнения программы. Получим что-то типа того, что программа во время своего выполнения может в реальном времени «узнавать» о своём состоянии и изменять своё поведение. Википедия же предлагает следующее определение: "Рефлексия означает процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения."

В PHP имеется очень мощный набор инструментов, позволяющий реализовать рефлексию. Рассмотрим некоторые инструменты для рефлексии, о которых уже знает почти каждый мало-мальски разбирающийся в PHP-разработчик. Языковые конструкции self и static, магические константы __DIR__ и __CLASS__, функции get_defined_vars(), func_get_args() или eval(), в конце концов возможность создавать объект класса, имя которого получено из переменной:

$obj = new $className();

а затем и вызов метода, название которого так же хранится в переменной:

$obj->$methodName();

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

Однако есть в PHP кое-что ещё более мощное, но требующее очень большой осторожности — это Reflection API. И прежде чем пойти дальше, я хочу чтобы Вы поняли, что те большие возможности, которые предоставляет этот инструмент, требуют от Вас такой же большой ответственности. Рефлексия это медленно. И самое главное — скорее всего Вашу задачу можно решить без использования рефлексии, но знать о ней настоящий профи обязан. А теперь поехали :)

PHP Reflection API – это набор классов, позволяющих вывести рефлексию на новый уровень. Эти классы называются рефлекторами. С помощью этих классов мы можем создавать объекты-рефлекторы для разных сущностей. Перейдём к практике и рассмотрим класс для рефлексии функций — ReflectionFunction.
Создадим новую функцию:

/**
 * @param $a
 * @param $b
 * @return int
 */
function sum($a, $b)
{
    return $a + $b;
}

Теперь создадим объект-рефлектор для неё:

$sumReflector = new ReflectionFunction('sum');

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

echo $sumReflector->getFileName();

или узнать строки её начала и конца:

echo $sumReflector->getStartLine();
echo $sumReflector->getEndLine();

как вы понимаете, этого уже достаточно для получения кода самой функции.
Ещё мы можем получить комментарий к функции в формате PHPDoc:

echo $sumReflector->getDocComment();

Распарсив такой комментарий мы можем затем выполнять вызов функции внутри какой-нибудь обёртки с передачей ей аргументов, приведённых к типу, указанному в PHPDoc’е.

Мы можем получить замыкание:

$closure = $sumReflector->getClosure();

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

Все методы мы рассматривать не будем, если стало интересно — почитайте документацию.

Рассмотрим теперь возможности рефлексии методов.
Создадим следующий класс:

class Post
{
    public $id;
    public $text;
    public $comments;

    public static function get(int $id) : self
    {
        $post = new self();
        $post->id = $id;
        $post->text = 'Test';
        $post->comments = $post->_getComments();

        return $post;
    }

    private function _getComments() : array
    {
        $comments = [
            1 => ['Comment for post #1'],
            2 => ['Comment for post #2']
        ];

        return $comments[$this->id];
    }
}

Создадим рефлектор для приватного метода _getComments():

$reflector = new ReflectionMethod('Post', '_getComments');

После чего нам доступны следующие интересные методы:
Является ли метод конструктором:

$reflector->isConstructor()

Является ли он абстрактным:

$reflector->isAbstract()

Является ли он приватным:

$reflector→isPrivate()

А теперь сделаем из метода замыкание и вызовем его, передав в параметре объект, контекст которого станет $this внутри этого замыкания:

$post = new Post();
$post->id = 2;
$closure = $reflector->getClosure($post);
$result = $closure();
var_dump($result);
Результат вызова замыкания будет следующим:

array(1) {
[0] =>
string(19) "Comment for post #2"
}

А теперь давайте сделаем метод публичным и вызовем его у объекта:

$post = new Post();
$post->id = 2;
$reflector->setAccessible(true);
$result = $reflector->invoke($post);
var_dump($result);

Результат:

array(1) {
[0] =>
string(19) "Comment for post #2"
}

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

Полный список методов рефлектора Вы можете посмотреть здесь.

Помимо этого можно создавать рефлекторы классов:
$reflector = new ReflectionClass('Post');

Список методов можно посмотреь здесь.

Наиболее интересные из них:
Получить все свойства:

->getProperties()

Получить все методы:

->getMethods()

Получить все константы:

->getConstants()

Создание нового объекта (даже с непубличным конструктором)

->newInstance()

Создание нового объекта без вызова конструктора (o_O)

->newInstanceWithoutConstructor()

Этих знаний достаточно, чтобы начать использовать Reflection API. Главное помните — использования этого инструмента следует по возможности избегать, и мне пока не приходилось использовать его нигде, кроме как в тестах. Здесь это оправдано, и временные потери во время выполнения тестов не так критичны, как, например, во время выполнения кода в продакшене. Все возможные рефлекторы можно изучить в официальной документации.

Комментарии

 Dram 2017-06-07 13:26:43
0

Какой-то нереальный скачок между прошлым уроком https://webshake.ru/post/746 и этим. Как будто-то с изи уровня сразу скакнул на хард.... ничего не понятно... это реально урок для "чайников" ?

Ответить
  •  cr3at0r 2017-06-07 14:54:14
    0

    Да, эти уроки не из курса, Вы правы. Сейчас уберу их из списка. Продолжение в разработке, новый урок примерно раз в неделю.

    Ответить
Чтобы написать комментарий нужно войти на сайт.
Или получить доступ прямо сейчас:


Нажимая эту кнопку
Вы принимаете политику конфиденциальности