PHP Reflection на замыканиях

Привет, Habr! Сегодня хочу рассказать про свой костыль, который помог мне не погружаться в дебри PHP Reflection. Ведь все пишут костыли, просто кто-то пишет большие, а кто-то поменьше.


image


Я активно использую 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


Это собственно все, что я хотел рассказать. Может до меня это уже изобрели, но в мою голову это идея пришла сама, поэтому я решил ею поделиться. Вдруг, это решение упростит кому-то жизнь.


Спасибо за внимание.

Метки:
Поделиться публикацией
Комментарии 16
  • +3
    Свойство было приватным. И никаких getter'ов для него в реализации класса не было. Изменять класс я конечно же не стал, ибо после composer update эта правка тут же слетит.

    собственно валидировать то, что не предоставляется публичным api библиотеки чревато тем же.

    • +3

      Вот если бы кто статью написал с паттернами мышления которые приводят к костылям и сомнительным решениям. Например.


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

      Представьте что вы идете по дороге и тут развилка. Вы выбрали направление, например пошли на леко. Через 10 минут дороги вы обнаруживаете что дороги больше нет, есть болото. Ваш выбор, вернуться назад и попробовать другой маршрут или "и так сойдет?".


      Я к тому что проблема у вас именно в этом месте, что вам зачем-то для валидатора понадобилось пробросить все правила валидации. И вместо того что бы решить эту проблему, настоящую проблему, вы ее решили игнорировать и в итоге потратили время решая другую.


      Оно конечно хорошо что вы разобрались с областями видимости пропертей, но как-то не очень хорошо так делать.

      • +1
        Передо мной стояла задача написать рекурсивную валидацию.
        Есть некоторый массив со своей структурой. И у него есть элемент, в котором вся эта структура дублируется.
        В идеале, я задаю правила для элементов и говорю, как называется «рекурсивный» элемент. Поэтому мне требовалось пробросить все правила внутрь, чтобы я мог применить их ко вложенным массивам.
      • 0
        Изменять класс я конечно же не стал, ибо после composer update эта правка тут же слетит.

        Не потому что слетит, а потому что это ужасное решение, не надо так :) Я слышал, в новой версии создание новых правил валидации станет более простым (ссылка).
        • 0
          Давно не использовал и не слежу за Laravel-ом, но судя по исходнику, свойство не приватное, а защищенное (protected), поэтому упомянутая проблема элементарно решается созданием дочернего класса Validator и описанием нужных методов — классика ООП.
          • 0
            Да, можно было сделать и так.
            Но в я посчитал, что такое вмешательство не несет вреда, так как я знаю что лежит внутри и использую эти данные только для чтения.
            • 0
              Соглашусь с orlov0562, там же Factory, она для того и создана чтобы ее можно было расширить если необходимо. Это был бы самый быстрый и красивый способ.

              Другой вопрос, который меня мучает: причем тут reflection?
              • 0
                Другой вопрос, который меня мучает: причем тут reflection?

                получение приватного свойста "как в reflection"

          • +1

            Автор, у меня вопрос: как Вы так читали отличную документацию на Reflection API, что в итоге даже не поняли, что это? Вы использовали стандартный метод класса Closure, применили позднее статическое связывание. Но никак не Reflection.
            Ну и да, соглашусь с предыдущими комментаторами — если Вам нужно зачем-либо получить доступ к приватным свойствам или методам чужого класса, значит Вы что-то делаете не так. Ищите другое решение.

            • 0
              Автор, у меня вопрос: как Вы так читали отличную документацию на Reflection API, что в итоге даже не поняли, что это? Вы использовали стандартный метод класса Closure, применили позднее статическое связывание. Но никак не Reflection.

              ну он и пишет — "reflection на замыканиях", т.е. отдает себе в этом отчет)

              • 0

                Сомневаюсь. Reflection тут вообще совсем не при чем:)

                • 0

                  он "как в reflection" получил доступ к приватному свойству.

            • 0

              Автор, вы, безусловно, молодец в том, что исследуете язык и ищете нестандартные способы решения поставленной задачи.
              PHP-сообщество сильно разделено в погоне за признанием, поэтому предпочитает тыкать носом во все неакадемичные решения.

              • 0

                Да пусть делает что хочет, лишь бы код этот не пришлось поддерживать кому-то еще. Такие вот "неакадемические" решения в итоге ведут к тому что обновить ту же ларавельку человеку уже будет не так просто. А люди которые "тыкают носом" просто хотят что бы человек задумался о средне и долгосрочных перспективах развития проекта и как его решения на это все влияют.


                А то всякие решатели проблем нарешают проблем, а как только некофмортно работать с кодом становятся идут "решать проблемы" куда-то еще.

              • 0
                Решение действительно выигрывает по объему кода и его концентрации (только в нужном месте).

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

                Совет интересный, спасибо, запомню.
                • 0
                  поступить вразрез с идеологией, нежели потратить в 3 раза больше времени (если время критичный ресурс).

                  тут есть очень опасный момент. С точки зрения бизнеса важна не только стоимость но и возможность эту стоимость предсказать. А используя подобные подходы мы создаем условия для рисков при изменения кода. Хотите обновить либку? А вот уже нельзя просто взять и обновить — надо протестить. И как правило "помнить" обо всех таких нюансах не выходит. А это значит что то что вы сэкономили час выльется потом в 3-4 часа отладки.


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

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