Привет, 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


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


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

Поделиться с друзьями
-->

Комментарии (14)


  1. zelenin
    15.07.2017 16:18
    +3

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

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


  1. Fesor
    15.07.2017 18:07
    +3

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


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

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


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


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


    1. HackerZhenya
      16.07.2017 10:24
      +1

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


  1. seniorcote
    15.07.2017 23:04

    Изменять класс я конечно же не стал, ибо после composer update эта правка тут же слетит.

    Не потому что слетит, а потому что это ужасное решение, не надо так :) Я слышал, в новой версии создание новых правил валидации станет более простым (ссылка).


  1. orlov0562
    16.07.2017 10:32

    Давно не использовал и не слежу за Laravel-ом, но судя по исходнику, свойство не приватное, а защищенное (protected), поэтому упомянутая проблема элементарно решается созданием дочернего класса Validator и описанием нужных методов — классика ООП.


    1. HackerZhenya
      16.07.2017 10:39

      Да, можно было сделать и так.
      Но в я посчитал, что такое вмешательство не несет вреда, так как я знаю что лежит внутри и использую эти данные только для чтения.


      1. theRavel
        17.07.2017 09:37

        Соглашусь с orlov0562, там же Factory, она для того и создана чтобы ее можно было расширить если необходимо. Это был бы самый быстрый и красивый способ.

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


        1. zelenin
          17.07.2017 10:41

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

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


  1. m0rtis
    16.07.2017 10:43
    +1

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


    1. zelenin
      16.07.2017 13:13

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

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


      1. m0rtis
        19.07.2017 05:31

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


        1. zelenin
          19.07.2017 10:05

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


  1. nitso
    18.07.2017 16:22

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


    1. Fesor
      19.07.2017 13:27

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


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