2 minutes to read, 6.06K views since 2016.10.11 Read in english

Как получить список свойств класса при помощи рефлексии в PHP

Reflection дает пользователю возможность получить информацию о классе, функциях (методе класса), свойствах класса и параметрах методов. Также Reflection дает возможность создавать экземпляры классов (даже с закрытыми конструкторами), вызывать методы классов изменять значения свойств, читать phpdoc'и и так далее.

Рассмотрим следующий класс Post:

class Post {

  public $id;

  /**
  * это название статьи 
  */
  public $title; 

  /**
  * это ее текст
  */  
  public $body; 

  public function save() {
    // этот метод должен сохранить обьект в базе данных (но это не точно)
  }

}

Представьте, что это класс модели, который связан с таблицей базы данных и может быть создан или обновлен в ней с помощью метода save ():

$somePost->save();

Для простоты понимания напишем что-то простенькое в теле метода сейв который это будет делать

public function save() {

    $sqlQuery = '';

    $setClause = '`title`="'.$this->title.'",'.
      '`body` = "'.$this->body.'"';

    if ($this->id > 0) {
      $sqlQuery = 'UPDATE `post` SET '.$setClause.' WHERE id = '.$this->id;
    } else {
      $sqlQuery = 'INSERT INTO `post` SET '.$setClause.', id = '.$this->id;
    }

    return $this->db->execute($sqlQuery);
}

Что мы можем сказать об этом коде? Он захардкожен и может использоваться только в случае этого же класса Post. И что произойдет, если мы добавим дополнительное поле, например, num_views, который сохранит количество просмотров поста? - Да, мы должны переписать наш метод save. И так с каждым классом? - да. Да, это печально и неинтересно

Как мы можем решить эту проблему в общем случае? один из способов - использовать рефлексию. Давайте же использовать ее!

Первое использование рефлексии по назначению

Давайте слегонца перепишем метод сейва, чтобы он был более гибким:

public function save() {

    $class = new \ReflectionClass($this);
    $tableName = strtolower($class->getShortName());

    $propsToImplode = [];

    foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) { // обрабатываем только public поля 
      $propertyName = $property->getName();
      $propsToImplode[] = '`'.$propertyName.'` = "'.$this->{$propertyName}.'"';
    }

    $setClause = implode(',',$propsToImplode); // склеиваим все пары ключ = значение запятыми 
    $sqlQuery = '';

    if ($this->id > 0) {
      $sqlQuery = 'UPDATE `'.tableName.'` SET '.$setClause.' WHERE id = '.$this->id;
    } else {
      $sqlQuery = 'INSERT INTO `'.tableName.'` SET '.$setClause.', id = '.$this->id;
    }

    try {
      return $this->db->execute($sqlQuery);
    } catch (\Exception $e) {
      // обрабатываем как-то исключение
    }
}

Вот смотрите, мы уже научились генерировать SQL запрос на обновление и вставку всех полей класса в бд, Не круто ли? - Круто.

Выходит написал клас, заэкстендил этот метод и вуаля - можно сразу сохранять в бд (пррр, не спешите - создание таблиц в mysql еще никто не отменял ) - да, выходит именно так.

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

Теперь мы можем использовать этот метод не только в классе Post, но и в любом классе, который должен иметь такую функциональность, и он будет работать, как мы ожидаем.

Я получаю свойства класса, который имеет только публичную видимость, предоставляя параметр для метода getProperties экземпляраReflectionClass:

$class->getProperties(\ReflectionProperty::IS_PUBLIC);

Такой подход позволит иметь в классах не только поля данных а и поля для внутренней спрятаной логики.

Метод getProperties возвращает массив объектовReflectionProperty. В этом уроке мы использовали только метод getName, который возвращает имя свойства. например, title для паблик поля обьявленого следующим образом:

public $title;

Вы можете опустить этот параметр, чтобы получить все свойства. Или вы можете получить поля с модификаторам private или protected наоборот.

Вы также можете проверить видимость поля с помощью методов ReflectionProperty:isPublic(), isPrivate() илиisProtected().

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

Read next article Implementation of dependency injection in php in course Reflection in PHP