PHP Reflection API

04.12.2016 в 10:37
33020
+2819

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

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

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

$obj = new $className();

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

$obj->$methodName();

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

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

Перейдём к практике и рассмотрим класс-рефлектор для функций — 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 (почитайте о PHPDoc, если до сих пор этого не сделали):

echo $sumReflector->getDocComment();

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

Рефлексия объектов

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

Сделаем это в контроллере для статей. И давайте сразу используем этот рефлектор для вывода свойств объекта Article.

src/MyProject/Controllers/ArticlesController.php

<?php

namespace MyProject\Controllers;

use MyProject\Models\Articles\Article;
use MyProject\View\View;

class ArticlesController
{
    /** @var View */
    private $view;

    public function __construct()
    {
        $this->view = new View(__DIR__ . '/../../../templates');
    }

    public function view(int $articleId)
    {
        $article = Article::getById($articleId);

        $reflector = new \ReflectionObject($article);
        $properties = $reflector->getProperties();
        var_dump($properties);
        return;
        ...

Посмотрим на результат:
Вывод свойств объекта с помощью PHP Reflection API

Мы видим массив объектов-рефлекторов для свойств объекта (ReflectionProperty). Они содержат два свойства - имя свойства и имя класса, в котором оно объявлено. Обратите внимание, что свойство id успешно унаследовано от класса ActiveRecordEntity.

А теперь давайте просто создадим массив, содержащий только имена свойств, в виде строк.

src/MyProject/Controllers/ArticlesController.php

    ...
    public function view(int $articleId)
    {
        $article = Article::getById($articleId);

        $reflector = new \ReflectionObject($article);
        $properties = $reflector->getProperties();
        $propertiesNames = [];
        foreach ($properties as $property) {
            $propertiesNames[] = $property->getName();
        }
        var_dump($propertiesNames);
        return;
        ...

Список полей объекта

Эти знания понадобятся нам в следующем уроке. А пока ещё несколько интересных методов для рефлектора объектов.

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

->getMethods()

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

->getConstants()

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

->newInstance()

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

->newInstanceWithoutConstructor()

Этих знаний достаточно, чтобы начать использовать Reflection API. Главное помните — использования этого инструмента следует по возможности избегать, так как это работает довольно медленно. Большинство задач можно решить без использования рефлексии, но знать о ней настоящий профи обязан. Иногда её использование позволяет создать довольно изящные решения, одно из которых мы рассмотрим в следующем уроке. А со всеми возможные рефлекторами можно ознакомиться в официальной документации. До встречи ;)

loader
04.12.2016 в 10:37
33020
+2819
Логические задачи с собеседований