Гидратор
4 июня 2017
В ноябре 2016 я написал и релизнул библиотеку Hydrator, но забыл её как надо анонсировать. Насколько знаю, термин «hydrator» первый раз был использован в Java ORM Hibernate. Задача гидратора — наполнить объект данными или получить данные из него. При этом важно не вызывать конструктор или геттеры-сеттеры. Это позволяет работать с private свойствами, которые сохраняются в базу или загружаются из неё, напрямую без открытия этих свойств. Интерфейс остаётся чистым.
Внутри используется PHP reflection.
Использование
Допустим, у нас есть сущность Post
, представляющая запись в блоге. У неё есть заголовок (title
) и текст (text
). Для каждой записи генерируется уникальный id.
class Post { private $id; protected $title; protected $text; public function __construct($title, $text) { $this->id = uniqid('post_', true); $this->title = $title; $this->text = $text; } public function getId() { return $this->id; } public function getTitle() { return $this->title; } public function setTitle($title) { $this->title = $title; } public function getText() { return $this->text; } public function setText() { return $this->text; } }
Сохраняем пост в базу:
$post = new Post('First post', 'Hell, it is a first post.'); $postHydrator = new \samdark\hydrator\Hydrator([ 'id' => 'id', 'title' => 'title', 'text' => 'text', ]); $data = $postHydrator->extract($post); save_to_database($data);
Загружаем пост из базы:
$data = load_from_database(); $postHydrator = new \samdark\hydrator\Hydrator([ 'id' => 'id', 'title' => 'title', 'text' => 'text', ]); $post = $postHydrator->hydrate($data, Post::class); echo $post->getId();
Заполняем существующий объект:
$data = load_from_database(); $postHydrator = new \samdark\hydrator\Hydrator([ 'title' => 'title', 'text' => 'text', ]); $post = get_post(); $post = $postHydrator->hydrateInto($data, $post); echo $post->getTitle();
Использование в Yii
Библиотека на данный момент не используется в самом фреймворке. Её можно использовать чтобы реализовать свой маппинг данных внутри репозитория в том случае, если вы решили делать проект следуя clean architecture и domain driven design.
Комментарии RSS по email OK
В чём смысл использовать отражения, если можно вызвать конструктор и воспользоваться геттерами и сеттерами?
Если всё равно Гидратор наполняет данными готовый класс, то что это даёт, кроме избавления от вызова сеттеров?
@higimo Если в сеттерах есть сложная логика проверки данных, то при использовании гидратора для заполнения данными приходится повторно прогонять все эти проверки. Это с точки зрения производительности.
С точки зрения логики, предполагается, что данные у нас корректные. И например, они были раньше сохранены в БД. Теперь мы загружаем данные из БД и заполняем класс гидратором. Логично же заполнить все напрямую, избежав лишних проверок в сеттерах?
Еще другой момент связан с тем, как сеттеры будут выполняться. Гидратор не формирует названия методов для вызова и заполнения класса (типа setVariableName), т.к. не знает правильные имена. Да и делать такую логику - излишество, ИМХО.
Если же сделать, чтобы гидратор напрямую делал вызовы в класс типа $object->variable_name, надеясь что у разработчика есть какая-то внутренняя магия с __set/__get - тоже глупая затея. Да и вызов этот больше времени занимает. Хотя, это надо проверять, что быстрее - использование магии __get/__set (со всеми неудобствами) или использовании Reflection (как в гидраторе).
higimo Есть смысл, использовать, когда атрибуты read-only. Например:
Хотя, по идее, вместо гидратора можно, по необходимости, к классам добавлять статический метод для восстановления состояния
Но это работает только в простых случаях, если в конструкторе нет дополнительной логики, а только маппинг данных.
Если, например в конструкторе еще идет обращение к диспетчеру событий, то без рефлексии, на сколько я знаю, не обойтись.
@ Stevad во-первых, нельзя доверять данным, полученным извне (в том числе и из своей БД). Поэтому пропуск валидации данных и заполнение объекта "грязными" данными приводит к снижению безопасности приложения. Во-вторых, рефлексия достаточно дорогая операция и тут еще надо смотреть, что будет быстрее - рефлексия или сетеры с валидаторами
А еще есть вот такой проект github.com/Ocramius/GeneratedHydrator поддерживающий несколько способов доступа к свойствам объекта и, по утверждению автора, очень быстрый
Oleg
По хорошему в сеттерах не нужно делать валидацию данных. Там достаточно, ну максимум, приведения типов. А валидация нужна на том уровне, где объект заполняется или создается - сервис, репозиторий и т.п.
@delvin согласен, что валидацию стоит делать не в сеттерах. Просто @Stevad заговорил про сложную логику проверки данных в сеттерах, я и ответил
@Oleg
Нельзя доверять данным, полученным от пользователя в любом виде. А вот то, что вы сохраняете в базу, должно быть обязательно корректным. Иначе какой смысл выполнять двойную проверку - сначала перед сохранением, а потом еще и при выгрузке данных. Это будет еще больше только замедлять приложение.
Stevad, к чему такая критика (причем высосанная из пальца)? Вас никто не принуждает использовать гидратор.