Всем любителям Symfony известно что в ней нету компонента фильтр.
Есть замечательные и удобные Constraints и даже была попытка создать на подобии их фильтры issue на Github, но никто так и не взялся это сделать.


Когда я в очередной раз столкнулся с задачей фильтрации большого количества данных, полученных от пользователя, я понял что пора решить задачу фильтрации как то более глобально/красиво/удобно.


Проведя некий research я понял что ничего кардинально нового в фильтрировании на php не появилось. Есть 2 популярных компонента:



Прошу заметить что последний не совсем хорошо поддерживается автором.
Для DMS-Filter есть symfony bundle но он не совместим с Symfony 3. Да и код его не понравился, так как я понимал что можно сделать все немного проще.


Проанализировав ситуацию, я понял что самый быстрый и надежный способ написать bundle для фильтрации который будет использовать хорошо документированные и покрытие тестами фильтры из Zend Filter. Так же хочу добавить что в прошлом я много работал с Zend Framework 1 и мне они были близки и понятны.


Мне хотелось сделать логику работы фильтров на подобии Symfony Constraints.
В итоге был написан bundle, что добавляет сервис который может быть использован для фильтрации объекта на основе аннотаций. Кроме того, bundle может фильтровать формы, если он находит аннотированный объект.


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



Мне кажется не стоит подробно описывать как пользоваться bundle в данной публикации так как она больше обьясняет зачем я его написал. Да и странно просто копировать информацию с одного места в другое.
Документацию по использованию bundla и сам bundle вы можете найти на GitHub FilterBundle.


Хотел бы узнать у сообщества, а как вы решаете задачи по фильтрации в Symfony?

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


  1. Fesor
    18.04.2016 15:40

    Всем любителям Symfony известно что в ней нету компонента фильтр.


    http://symfony.com/doc/current/components/options_resolver.html и filter_var, а так же фильтры твига (для фильтрации при выводе) полностью закрывают все юзкейсы.

    а как вы решаете задачи по фильтрации в Symfony?

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


    1. Bukashk0zzz
      18.04.2016 15:51

      Думаю Вы просто мало с этим стыкались. Пример: как легко и просто можно фильтровать данные которые вы получаете от пользователя в написаном вами API? Или фильтрация данных из entity когда entity нужно передать в serializer/сторонний api?


      1. Fesor
        18.04.2016 18:54

        Для начала давайте разберемся, какая у меня позиция относительно фильтрации данных в сущностях. Фильтровать данные в сущностях должны сами сущности (то есть я их тех людей, которые против использования сеттеров, анемичной модели и прочей чуши). У меня сущности это полноценные объекты предметной области, а не тупые структурки данных.

        Далее…

        как легко и просто можно фильтровать данные которые вы получаете от пользователя в написаном вами API?


        Из такого рода «фильтрации» мне нужно только trim строк. Предложите мне другие ситуации когда это нужно?


        1. Bukashk0zzz
          18.04.2016 23:50

          Конкретный пример из жизни. У вас есть объект который вы получили с сервиса/базы данных которую редактировать у вас нет доступа. В объекте нужно отфильтровать несколько полей перед тем как передать конечному клиенту/сервису.
          Исходя из вашего личного правила что в set/get можно писать фильтрацию и подобные вещи эта проблема решаема. Но куда удобней добавить одну аннотацию к property вместо нескольких строчек кода. Особенно если у вас много таких объектов и они совсем не маленькие. С моим решением как минимум станет легче читать код.


          1. Fesor
            19.04.2016 00:05

            Вы не поняли. Что именно мне нужно фильтровать? Вот… серьезно, я не могу придумать.

            Исходя из вашего личного правила что в set/get можно писать фильтрацию


            Мое правило — нет никаких get/set в сущностях (только очень редко геттеры и только те что нужно). Есть только объекты предметной области, полноценные сущности с логикой внутри.

            Особенно если у вас много таких объектов и они совсем не маленькие.

            В большинстве случаев жирные объекты с большим количеством полей свидетельствуют о недостаточном уровне декомпозиции.


            1. Bukashk0zzz
              19.04.2016 10:23

              >В большинстве случаев жирные объекты с большим количеством полей свидетельствуют о недостаточном уровне декомпозиции.
              Да но вы же не можете повлиять на нормализацию сторонней базы данных.

              >Что именно мне нужно фильтровать? Вот… серьезно, я не могу придумать.
              Например:
              — в varchar хранится int и вам именно int нужно передать дальше.
              — есть текст с переносами \n вам нужно сделать
              — есть html а нужно передать text
              — есть datatime а нужно timestamp


              1. Fesor
                19.04.2016 11:29

                Да но вы же не можете повлиять на нормализацию сторонней базы данных.


                Я могу мэпить ее на ту структуру объектов с которой мне приятно работать. В прочем у меня нет задач таких где я «не могу на что-то влиять» в плане структуры базы.

                далее.

                — каст типов — OptionsResolver
                — это «фильтрация» при выводе, с этим нет никаких проблем. К сущностям не имеет отношения и к хранению тоже.
                — есть html а нужно text — так же фильтрация строго при выводе. К сущностям не имеет отношения и к хранению тоже.
                — есть datetime а нужно timestamp — вопрос фильтрации вывода. К сущностям не имеет отношения и к хранению тоже.


                1. Bukashk0zzz
                  19.04.2016 11:39

                  А как можно удобно фильтровать данные при вывод если вы используете Serializer + FOSRest?
                  С учетом того что выводимые данные в таком случае описываются в модели http://jmsyst.com/libs/serializer/master/reference/annotations


                  1. Fesor
                    19.04.2016 12:03

                    Я настрадался с сериалайзерами и просто использую fractal. Да и с symfony serializer я просто свои хэндлеры объектов писал. Код выходит мегаскучный, зато предсказуемый.


                  1. Fesor
                    19.04.2016 12:15

                    Ну и да, для справки. JMS Serializer — старый, не поддерживается уже года два, в нем куча багов и «ускорения разработки» с ним нет никакого. Я его 2 года использовал, мне надоело.


                    1. Bukashk0zzz
                      19.04.2016 12:16

                      Но есть же проекты которым уже не один год) В таких случаях достаточно сложно перейти на что то другое.


                1. shoomyst
                  19.04.2016 13:05

                  Под выводом подразумевается хелпер твига? Где хранится код трансформации? В самом хелпере или хелпер просто проксирует запросы на другие объекты, передаваемые в хелпер через DI?


                  1. Fesor
                    19.04.2016 15:46

                    Чта?

                    Фильтры твига нужны там где есть твиг. А фильтровать тот же HTML в API не нужно, это сделают на клиенте.


                    1. shoomyst
                      19.04.2016 16:15

                      Понятно, echo json_encode(mysql_fetch_assoc($rowset));


                      1. Fesor
                        19.04.2016 17:53

                        Ну между json_encode и mysql_fetch_assoc еще гидрации Doctrine и много других веселых этапов. В частности надо же как-то еще DateTIme сериализовать в формате ATOM и т.д. Но это не фильтрация.


      1. lowadka
        18.04.2016 18:57

        Мы фильтруем так https://habrahabr.ru/post/281875/


  1. ewgRa
    18.04.2016 18:14

    Очень похоже на Data Transformers. Вам не это надо было?


    1. lowadka
      18.04.2016 18:25
      +1

      Отвечу за автора: эту задачу можно решить с помощью Data Transformer`ов, «запихнув» фильтрацию в этот слой, но сами трансформеры задачу не решают. Автор же предлагает простой вариант с аннотациями, для быстрой разработки и простого понимания


      1. ewgRa
        18.04.2016 21:32
        +1

        Дык можно решить, или трансформеры задачу не решают?

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


        1. Bukashk0zzz
          18.04.2016 23:28

          Мою задачу одними Data Transformer`ами не решить. Они работают для форм, но любой объект ими не отфильтруешь. Например есть база данных сторонняя и нету возможности изменять её формат и данные, но получать информацию можно. В моем решении можно отфильтровать любой объект с аннотациями в любой нужный момент.


          1. ewgRa
            19.04.2016 00:26

            Я так понимаю вам фильтровать надо, чтобы потом дальше передавать куда-нибудь? Во View, или куда-нибудь в др. место?

            Расскажите как вы будете это делать если у вас один компонент допускает только br в name сущности, другой компонент допускает только b в name сущности. Сущность одна, приемников несколько, но в пропертях вы можете задать только один фильтр. Ваши действия?

            Лично мое мнение, подобные преобразования должны происходить ближе к «выводу» и знания, как будет модель преобразовываться точно должны быть не в модели. Например View знает, что что-то надо показывать raw, а что-то с escape.

            В общем на мой взгляд спорное решение.


            1. Bukashk0zzz
              19.04.2016 10:30

              > Расскажите как вы будете это делать если у вас один компонент допускает только br в name сущности, другой компонент допускает только b в name сущности. Сущность одна, приемников несколько, но в пропертях вы можете задать только один фильтр. Ваши действия?

              Использовать группы фильтрации. Ровно так же как это сделано с валидацией.

              > В общем на мой взгляд спорное решение.
              Исходя из ваших слов выходит что и Constraint в symfony сделаны не верно.

              Я долго работал с Zend и после перехода на Symfony самое первое что мне бросилось в глаза это простота/удобство аннотаций. Раньше я все валидации/фильтрации делал в формах и не могу сказать что это удобно.


              1. ewgRa
                19.04.2016 10:44

                Constraints это скорее для более удобной работы со схемой. Мне кажется тут больше надо смотреть в сторону Data Transfer Object.


        1. lowadka
          19.04.2016 00:18

          Если задачу решить через трансформеры, то это будет так же «в двух местах», только при этом в двух совсем разных местах. А в случае автора, если он использует аннотации, то «валидация» хоть и будет два раза, но описана она будет в одном месте, что имхо лучше.


          1. AmdY
            19.04.2016 13:26

            В этом и беда, что в одном месте. Например, переносы строк для вывода в теге «p» проходят через «nl2br», а вот при выводе в форме нужно использовать «htmlspecialchars», но никак не «nl2br».

            Подобную логику нельзя сунуть в модели, можно делать отдельный сервис в стиле декоратора вроде view presenter или data transformer