Всем любителям Symfony известно что в ней нету компонента фильтр.
Есть замечательные и удобные Constraints и даже была попытка создать на подобии их фильтры issue на Github, но никто так и не взялся это сделать.
Когда я в очередной раз столкнулся с задачей фильтрации большого количества данных, полученных от пользователя, я понял что пора решить задачу фильтрации как то более глобально/красиво/удобно.
Проведя некий research я понял что ничего кардинально нового в фильтрировании на php не появилось. Есть 2 популярных компонента:
Прошу заметить что последний не совсем хорошо поддерживается автором.
Для DMS-Filter есть symfony bundle но он не совместим с Symfony 3. Да и код его не понравился, так как я понимал что можно сделать все немного проще.
Проанализировав ситуацию, я понял что самый быстрый и надежный способ написать bundle для фильтрации который будет использовать хорошо документированные и покрытие тестами фильтры из Zend Filter. Так же хочу добавить что в прошлом я много работал с Zend Framework 1 и мне они были близки и понятны.
Мне хотелось сделать логику работы фильтров на подобии Symfony Constraints.
В итоге был написан bundle, что добавляет сервис который может быть использован для фильтрации объекта на основе аннотаций. Кроме того, bundle может фильтровать формы, если он находит аннотированный объект.
В процессе написания модуля сложностей не возникло. Вот разделы документации которые я использовал во время написания:
- How to Create a Form Type Extension
- Form Events
- How to Dynamically Modify Forms Using Form Events
- How to Register Event Listeners and Subscribers
Мне кажется не стоит подробно описывать как пользоваться bundle в данной публикации так как она больше обьясняет зачем я его написал. Да и странно просто копировать информацию с одного места в другое.
Документацию по использованию bundla и сам bundle вы можете найти на GitHub FilterBundle.
Хотел бы узнать у сообщества, а как вы решаете задачи по фильтрации в Symfony?
Комментарии (25)
ewgRa
18.04.2016 18:14Очень похоже на Data Transformers. Вам не это надо было?
lowadka
18.04.2016 18:25+1Отвечу за автора: эту задачу можно решить с помощью Data Transformer`ов, «запихнув» фильтрацию в этот слой, но сами трансформеры задачу не решают. Автор же предлагает простой вариант с аннотациями, для быстрой разработки и простого понимания
ewgRa
18.04.2016 21:32+1Дык можно решить, или трансформеры задачу не решают?
Насчет понимания я бы поспорил. Валидация в двух местах теперь, рано или поздно это где-нибудь аукнется. Насчет быстрой разработки, тут спорить не буду, но быстро не всегда значит правильно.Bukashk0zzz
18.04.2016 23:28Мою задачу одними Data Transformer`ами не решить. Они работают для форм, но любой объект ими не отфильтруешь. Например есть база данных сторонняя и нету возможности изменять её формат и данные, но получать информацию можно. В моем решении можно отфильтровать любой объект с аннотациями в любой нужный момент.
ewgRa
19.04.2016 00:26Я так понимаю вам фильтровать надо, чтобы потом дальше передавать куда-нибудь? Во View, или куда-нибудь в др. место?
Расскажите как вы будете это делать если у вас один компонент допускает только br в name сущности, другой компонент допускает только b в name сущности. Сущность одна, приемников несколько, но в пропертях вы можете задать только один фильтр. Ваши действия?
Лично мое мнение, подобные преобразования должны происходить ближе к «выводу» и знания, как будет модель преобразовываться точно должны быть не в модели. Например View знает, что что-то надо показывать raw, а что-то с escape.
В общем на мой взгляд спорное решение.Bukashk0zzz
19.04.2016 10:30> Расскажите как вы будете это делать если у вас один компонент допускает только br в name сущности, другой компонент допускает только b в name сущности. Сущность одна, приемников несколько, но в пропертях вы можете задать только один фильтр. Ваши действия?
Использовать группы фильтрации. Ровно так же как это сделано с валидацией.
> В общем на мой взгляд спорное решение.
Исходя из ваших слов выходит что и Constraint в symfony сделаны не верно.
Я долго работал с Zend и после перехода на Symfony самое первое что мне бросилось в глаза это простота/удобство аннотаций. Раньше я все валидации/фильтрации делал в формах и не могу сказать что это удобно.ewgRa
19.04.2016 10:44Constraints это скорее для более удобной работы со схемой. Мне кажется тут больше надо смотреть в сторону Data Transfer Object.
lowadka
19.04.2016 00:18Если задачу решить через трансформеры, то это будет так же «в двух местах», только при этом в двух совсем разных местах. А в случае автора, если он использует аннотации, то «валидация» хоть и будет два раза, но описана она будет в одном месте, что имхо лучше.
AmdY
19.04.2016 13:26В этом и беда, что в одном месте. Например, переносы строк для вывода в теге «p» проходят через «nl2br», а вот при выводе в форме нужно использовать «htmlspecialchars», но никак не «nl2br».
Подобную логику нельзя сунуть в модели, можно делать отдельный сервис в стиле декоратора вроде view presenter или data transformer
Fesor
http://symfony.com/doc/current/components/options_resolver.html и filter_var, а так же фильтры твига (для фильтрации при выводе) полностью закрывают все юзкейсы.
Не фильтрую. Только при выводе. trim-минги формы сами делают, иногда можно в сущностях что-то пофильтровать, но это именно какие-то бизнес-ограничения и деталие реализации сущностей и там этим вещам и место. А так это просто не нужно. Ну или у меня таких задач нет.
Bukashk0zzz
Думаю Вы просто мало с этим стыкались. Пример: как легко и просто можно фильтровать данные которые вы получаете от пользователя в написаном вами API? Или фильтрация данных из entity когда entity нужно передать в serializer/сторонний api?
Fesor
Для начала давайте разберемся, какая у меня позиция относительно фильтрации данных в сущностях. Фильтровать данные в сущностях должны сами сущности (то есть я их тех людей, которые против использования сеттеров, анемичной модели и прочей чуши). У меня сущности это полноценные объекты предметной области, а не тупые структурки данных.
Далее…
Из такого рода «фильтрации» мне нужно только trim строк. Предложите мне другие ситуации когда это нужно?
Bukashk0zzz
Конкретный пример из жизни. У вас есть объект который вы получили с сервиса/базы данных которую редактировать у вас нет доступа. В объекте нужно отфильтровать несколько полей перед тем как передать конечному клиенту/сервису.
Исходя из вашего личного правила что в set/get можно писать фильтрацию и подобные вещи эта проблема решаема. Но куда удобней добавить одну аннотацию к property вместо нескольких строчек кода. Особенно если у вас много таких объектов и они совсем не маленькие. С моим решением как минимум станет легче читать код.
Fesor
Вы не поняли. Что именно мне нужно фильтровать? Вот… серьезно, я не могу придумать.
Мое правило — нет никаких get/set в сущностях (только очень редко геттеры и только те что нужно). Есть только объекты предметной области, полноценные сущности с логикой внутри.
В большинстве случаев жирные объекты с большим количеством полей свидетельствуют о недостаточном уровне декомпозиции.
Bukashk0zzz
>В большинстве случаев жирные объекты с большим количеством полей свидетельствуют о недостаточном уровне декомпозиции.
Да но вы же не можете повлиять на нормализацию сторонней базы данных.
>Что именно мне нужно фильтровать? Вот… серьезно, я не могу придумать.
Например:
— в varchar хранится int и вам именно int нужно передать дальше.
— есть текст с переносами \n вам нужно сделать
— есть html а нужно передать text
— есть datatime а нужно timestamp
Fesor
Я могу мэпить ее на ту структуру объектов с которой мне приятно работать. В прочем у меня нет задач таких где я «не могу на что-то влиять» в плане структуры базы.
далее.
— каст типов — OptionsResolver
— это «фильтрация» при выводе, с этим нет никаких проблем. К сущностям не имеет отношения и к хранению тоже.
— есть html а нужно text — так же фильтрация строго при выводе. К сущностям не имеет отношения и к хранению тоже.
— есть datetime а нужно timestamp — вопрос фильтрации вывода. К сущностям не имеет отношения и к хранению тоже.
Bukashk0zzz
А как можно удобно фильтровать данные при вывод если вы используете Serializer + FOSRest?
С учетом того что выводимые данные в таком случае описываются в модели http://jmsyst.com/libs/serializer/master/reference/annotations
Fesor
Я настрадался с сериалайзерами и просто использую fractal. Да и с symfony serializer я просто свои хэндлеры объектов писал. Код выходит мегаскучный, зато предсказуемый.
Fesor
Ну и да, для справки. JMS Serializer — старый, не поддерживается уже года два, в нем куча багов и «ускорения разработки» с ним нет никакого. Я его 2 года использовал, мне надоело.
Bukashk0zzz
Но есть же проекты которым уже не один год) В таких случаях достаточно сложно перейти на что то другое.
shoomyst
Под выводом подразумевается хелпер твига? Где хранится код трансформации? В самом хелпере или хелпер просто проксирует запросы на другие объекты, передаваемые в хелпер через DI?
Fesor
Чта?
Фильтры твига нужны там где есть твиг. А фильтровать тот же HTML в API не нужно, это сделают на клиенте.
shoomyst
Понятно, echo json_encode(mysql_fetch_assoc($rowset));
Fesor
Ну между json_encode и mysql_fetch_assoc еще гидрации Doctrine и много других веселых этапов. В частности надо же как-то еще DateTIme сериализовать в формате ATOM и т.д. Но это не фильтрация.
lowadka
Мы фильтруем так https://habrahabr.ru/post/281875/