Привет, Habr! Сегодня хочу рассказать про свой костыль, который помог мне не погружаться в дебри PHP Reflection. Ведь все пишут костыли, просто кто-то пишет большие, а кто-то поменьше.
Я активно использую Laravel в своих проектах. Для тех, кто не знаком с этим framework'ом — не отчаивайтесь, потому что я объясню непонятные моменты.
В этот раз я писал некоторое расширение правил валидации:
Validator::extend('someRule', function ($attribute, $value, $parameters, $validator) {
// some code...
return $result; // boolean
}, ':attribute is invalid');
И мне потребовалось получить список всех правил, передаваемых валидатору. Как оказалось, эта простая на первый взгляд задача заняла у меня немного больше времени, чем я планировал. Свойство было приватным. И никаких getter'ов для него в реализации класса не было. Изменять класс я конечно же не стал, ибо после composer update эта правка тут же слетит.
Следует сказать, что раньше я никогда не использовал Reflection, но слышал, для чего оно используется. Так вот, я начал читать документацию. Естественно, код из примера с первого раза не завелся и нужно было искать еще. И тут я подумал, что решение должно быть проще.
И я нашел таки простой и элегантный костыль. Все-таки так лучше не делать. Приватные свойства на то и приватные, чтобы туда не лазили. Но обстоятельства требуют, так что...
Validator::extend('someRule', function ($attribute, $value, $parameters, $validator) {
// Это функция-ниндзя. Она врывается в валидатор и крадет приватное свойство.
// Это очень коварно и подло, но у меня нет другого выбора (есть, но там писать больше)
$ninja = function() {
// именно в этом свойстве хранится массив с нужными мне данными
return $this->initialRules;
};
$initialRules = $ninja->call($validator); // параметр $newThis
// some code
return $result;
}, ':attribute is invalid');
Если кто-то еще не понял, объясню: я создал анонимную функцию, которая возвращает некоторое свойство. А потом просто подменил контекст на контекст валидатора (laravel передает экземпляр этого класса). Тоесть, замыкание теперь имеет доступ к этому объекту изнутри и может получить доступ к любому приватному свойству и методу.
Работает вся это красота, начиная с PHP 5.4
Это собственно все, что я хотел рассказать. Может до меня это уже изобрели, но в мою голову это идея пришла сама, поэтому я решил ею поделиться. Вдруг, это решение упростит кому-то жизнь.
Спасибо за внимание.
Комментарии (14)
Fesor
15.07.2017 18:07+3Вот если бы кто статью написал с паттернами мышления которые приводят к костылям и сомнительным решениям. Например.
И мне потребовалось получить список всех правил, передаваемых валидатору.
Представьте что вы идете по дороге и тут развилка. Вы выбрали направление, например пошли на леко. Через 10 минут дороги вы обнаруживаете что дороги больше нет, есть болото. Ваш выбор, вернуться назад и попробовать другой маршрут или "и так сойдет?".
Я к тому что проблема у вас именно в этом месте, что вам зачем-то для валидатора понадобилось пробросить все правила валидации. И вместо того что бы решить эту проблему, настоящую проблему, вы ее решили игнорировать и в итоге потратили время решая другую.
Оно конечно хорошо что вы разобрались с областями видимости пропертей, но как-то не очень хорошо так делать.
HackerZhenya
16.07.2017 10:24+1Передо мной стояла задача написать рекурсивную валидацию.
Есть некоторый массив со своей структурой. И у него есть элемент, в котором вся эта структура дублируется.
В идеале, я задаю правила для элементов и говорю, как называется «рекурсивный» элемент. Поэтому мне требовалось пробросить все правила внутрь, чтобы я мог применить их ко вложенным массивам.
seniorcote
15.07.2017 23:04Изменять класс я конечно же не стал, ибо после composer update эта правка тут же слетит.
Не потому что слетит, а потому что это ужасное решение, не надо так :) Я слышал, в новой версии создание новых правил валидации станет более простым (ссылка).
orlov0562
16.07.2017 10:32Давно не использовал и не слежу за Laravel-ом, но судя по исходнику, свойство не приватное, а защищенное (protected), поэтому упомянутая проблема элементарно решается созданием дочернего класса Validator и описанием нужных методов — классика ООП.
HackerZhenya
16.07.2017 10:39Да, можно было сделать и так.
Но в я посчитал, что такое вмешательство не несет вреда, так как я знаю что лежит внутри и использую эти данные только для чтения.theRavel
17.07.2017 09:37Соглашусь с orlov0562, там же Factory, она для того и создана чтобы ее можно было расширить если необходимо. Это был бы самый быстрый и красивый способ.
Другой вопрос, который меня мучает: причем тут reflection?zelenin
17.07.2017 10:41Другой вопрос, который меня мучает: причем тут reflection?
получение приватного свойста "как в reflection"
m0rtis
16.07.2017 10:43+1Автор, у меня вопрос: как Вы так читали отличную документацию на Reflection API, что в итоге даже не поняли, что это? Вы использовали стандартный метод класса Closure, применили позднее статическое связывание. Но никак не Reflection.
Ну и да, соглашусь с предыдущими комментаторами — если Вам нужно зачем-либо получить доступ к приватным свойствам или методам чужого класса, значит Вы что-то делаете не так. Ищите другое решение.zelenin
16.07.2017 13:13Автор, у меня вопрос: как Вы так читали отличную документацию на Reflection API, что в итоге даже не поняли, что это? Вы использовали стандартный метод класса Closure, применили позднее статическое связывание. Но никак не Reflection.
ну он и пишет — "reflection на замыканиях", т.е. отдает себе в этом отчет)
nitso
18.07.2017 16:22Автор, вы, безусловно, молодец в том, что исследуете язык и ищете нестандартные способы решения поставленной задачи.
PHP-сообщество сильно разделено в погоне за признанием, поэтому предпочитает тыкать носом во все неакадемичные решения.Fesor
19.07.2017 13:27Да пусть делает что хочет, лишь бы код этот не пришлось поддерживать кому-то еще. Такие вот "неакадемические" решения в итоге ведут к тому что обновить ту же ларавельку человеку уже будет не так просто. А люди которые "тыкают носом" просто хотят что бы человек задумался о средне и долгосрочных перспективах развития проекта и как его решения на это все влияют.
А то всякие решатели проблем нарешают проблем, а как только некофмортно работать с кодом становятся идут "решать проблемы" куда-то еще.
zelenin
собственно валидировать то, что не предоставляется публичным api библиотеки чревато тем же.