Фильтры: смерть регуляркам и правильная валидация

    Фильтры данных впервые появились в PHP 5.0, и по какому-то стечению обстоятельств остались незамечеными большй частью кодеров. Наверное, это можно объяснить отсутствием чего-нибудь подобного в PHP4, а может просто мануал плохо читали. Я тоже узнал про них случайно… А ведь эта замечательная функция позволяет избавиться от запутаных, и подчас некорректных, регулярных выражений, при выполнении типичных задач.
    Мануал по фильтрам находится здесь. Приведу только основную информацию и пару практичных примеров.

    Ближе к делу


    Во-первых, фильтры разделены на 3 типа:
    1. Validate Filters — проверяющие фильтры
    2. Sanitize Filters — обезопашивающие фильтры
    3. Other Filters — другие фильтры

    Фильтры первого типа проверяют соответствие строки фильтру. Ответ от такого фильтра — исходная строка в случае удачи или false.
    Фильтры второго типа обрабатывают строку и возвращают её в отфильтрованном виде.
    Третий тип включает только один фильтр — FILTER_CALLBACK, который передаст строку пользовательской функции и вернет её ответ.

    Основная функция для работы с фильтрами — filter_var:
    mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )

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

    На последок — пара примеров работы:
    
    // Проверка e-mail
    $var = 'mail@example.com';
    var_dump(filter_var($var, FILTER_VALIDATE_EMAIL));
    $var = 'mail@exa_mple.com';
    var_dump(filter_var($var, FILTER_VALIDATE_EMAIL));
    
    // Проверка IP
    $var = '2001:471:1f11:251:290:27ff:fee0:2093';
    // Это IPv4?
    var_dump(filter_var($var, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4));
    // Может, это IPv6?
    var_dump(filter_var($var, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6));
    
    Поделиться публикацией
    Комментарии 93
      +24
      Вещь полезная, но «смерть регуляркам» — громко сказано: они годятся только для валидации.
        +3
        Вероятно, внутренняя реализация этих фильтров всё же сводится к прекомпилированному регекспу :-)
          +5
          По сути не «смерть регуляркам», а «инкапсулируйте свой код». Никто не мешал и до этого на системном слое приложения написать свою функцию validateEmail, которая внутри содержит правильный регексп, и использовать эту функцию, где надо, а не писать регекспы в бизнес-логике. Тогда с появлением кириллических доменов вы патчите свою функцию и радуетесь жизни дальше. PHP просто предоставил предопределённый набор функций. Почему нет, но причём здесь регекспы?
            +1
            Кстати о регекспах на email, случайно не копались в исходниках Swift mailer? ;)
            +1
            возможно только самому писать регулярное выражение на проверку email в соответствии с RFC слишком сложно, см. www.ex-parrot.com/pdw/Mail-RFC822-Address.html
              0
              упс… уже писали :)
                +2
                никогда не понимал этого фанатизма с проверкой в соответствии с RFC.
                имхо, такой фильтр должен пропускать все, что корректно поймет программа, которой мы дальше этот адрес передаем (sendmail, например).

                пример: при установке локально всяких движков (для тестирования), требующих email вбиваю адрес admin@localhost
                возможно, он «расово неправильный», но отправленное туда через mail() письмо прекрасно доходит куда мне надо.
                но некоторые «особо умные» скрипты на это начинают ругаться.
                другой пример: новые зоны первого уровня, тот же рф.

                проверка все же должна помогать пользователю (что он в поле email нечаянно не ввел что-то другое), а не мешать.
                  +1
                  Дык, RFC 822 очень много пропускает.
                  и admin@localhost пропускает.
                  habrahabr.ru/blogs/php/108475/#comment_3434810
                    0
                    А что не так с admin@localhost?
                      +1
                      Здесь «пропускает» значит «позволяет».
                      admin@localhost — нормальный адрес по мнению модуля Mail::RFC822::Address.
                        0
                        Я это понимаю.
                        А этот адрес «ненормальный»?
                          0
                          PHP говорит что ненормальный

                          php -r 'var_dump(filter_var("admin@localhost", FILTER_VALIDATE_EMAIL));'
                          bool(false)
                          
                            0
                            Ну пхп можно ещё простить. Но мой комментарий адресовался изначально OlegTar, который, судя по его комментарию, считает, что этот адрес невалидный.
                              0
                              Я не считаю этот адрес невалидным.
                                0
                                А, тогда пардон, показалось :-)
                        0
                        Всё так, я не против этого адреса. Просто zibada думал, что возможно RFC822 его не пропускает.
                      +2
                      Так именно по этой причине и надо соответствовать RFC, а не своим догадкам какой email может быть. Мне уже далеко не раз приходилось доделывать чужую валидацию (email'ов в том числе), потому что предыдущий программист считает, что RFC — это для зануд. Перед ем как работать со стандартами — ВСЕГДА почитай сначала RFC!
                +4
                Пхп далеко… интересно, а такой фильтр:
                $var = 'mail@example.com';
                var_dump(filter_var($var, FILTER_VALIDATE_EMAIL));

                на e-mail: «медведев@президент.рф» споткнется? Т.е. он адекватно кушает utf-8 и предполагает наличие других языков вообще?
                  0
                  К сожалению, нет
                    +6
                    shock@localhost:~> php -r "var_dump(filter_var('медведев@президент.рф', FILTER_VALIDATE_EMAIL));"
                    bool(false)
                    shock@localhost:~> php -v
                    PHP 5.3.2 (cli)
                    Copyright © 1997-2010 The PHP Group
                    Zend Engine v2.3.0, Copyright © 1998-2010 Zend Technologies

                    К счастью, споткнулся)
                      +2
                      К чьему, блин, счастью то? Вам клиент\начальник скажет — хочу поддержку кириллических доменов и никуда вы не денетесь. Еба**ся придется как всегда технарям…
                        +19
                        за его деньги — хоть китайские
                          +14
                          сконвертить в xn-- это просто ну архисложная задача, да…
                            0
                            Действительно, ведь и через почтовик нельзя отправить письмо на медведев@президент.рф
                            В любом случае сначала в punycode.
                      0
                      Голову на отсечение даю, что споткнется!
                      +2
                      вообще-то в адресе запрещены не ascii символы, так что с русским еще предстоит поиметь секса.
                        +1
                        Validates value as URL (according to » www.faqs.org/rfcs/rfc2396), optionally with required components. Note that the function will only find ASCII URLs to be valid; internationalized domain names (containing non-ASCII characters) will fail.
                        +4
                        Раз уж смерть регуляркам — было бы классно если бы вы показали сравнение по скорости например этого email фильтра и регулярного выражения.
                          –8
                          Регулярки — это как секс с настоящей женщиной, фильтры — как секс с женщиной резиновой. :)
                          И никаких сравнений скорости не надо. :)
                          • НЛО прилетело и опубликовало эту надпись здесь
                              0
                              Зато когда финал… :))
                                +1
                                смерть регуляркам означает что мы останемся без секса? (:
                              0
                              по скооорости?
                              это разве-что для спамбота актуально. неужели вы проверяете тысячи email адресов в секунду? или откуда эта забота о скорости?

                              мне кажеться, что на проверку ВСЕХ email адресов в небольшом проекте на несколько десятков тысяч юзеров (ха!) уйдет меньше времени, чем вы потратили на прочтение этого комментария.
                              +37
                              Вы бы в код глянули, прежде чем писать
                              void php_filter_validate_email(PHP_INPUT_FILTER_PARAM_DECL) /* {{{ */
                              {
                                /* From cvs.php.net/co.php/pear/HTML_QuickForm/QuickForm/Rule/Email.php?r=1.4 */
                                const char regexp[] = "/^((\\\"[^\\\"\\f\\n\\r\\t\\b]+\\\")|([A-Za-z0-9_][A-Za-z0-9_\\!\\#\\$\\%\\&\\'\\*\\+\\-\\~\\/\\=\\?\\^\\`\\|\\{\\}]*(\\.[A-Za-z0-9_\\!\\#\\$\\%\\&\\'\\*\\+\\-\\~\\/\\=\\?\\^\\`\\|\\{\\}]*)*))@((\\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9])(([A-Za-z0-9\\-])*([A-Za-z0-9]))?(\\.(?=[A-Za-z0-9\\-]))?)+[A-Za-z]+))$/D";

                                pcre    *re = NULL;
                                pcre_extra *pcre_extra = NULL;
                                int preg_options = 0;
                                int     ovector[150]; /* Needs to be a multiple of 3 */
                                int     matches;

                                re = pcre_get_compiled_regex((char *)regexp, &pcre_extra, &preg_options TSRMLS_CC);
                                if (!re) {
                                  RETURN_VALIDATION_FAILED
                                }
                                matches = pcre_exec(re, NULL, Z_STRVAL_P(value), Z_STRLEN_P(value), 0, 0, ovector, 3);

                                /* 0 means that the vector is too small to hold all the captured substring offsets */
                                if (matches < 0) {
                                  RETURN_VALIDATION_FAILED
                                }

                              }

                              * This source code was highlighted with Source Code Highlighter.

                              Теже самые регулярки, которым смерть :)
                              Только вот что это за фигня я затрудняюсь сказать, поскольку RFC она не соответвует.
                                +1
                                походу это просто удобная обертка для типовых операций валидации))

                                PS: объем regex'a доставляет))
                                  +12
                                  Это фигня, а не регулярка. Вот тру:
                                  pastebin.com/rLGH1wtk
                                    +1
                                    писец))
                                    а что она проверяет?
                                      +7
                                      Другие регулярные выражения.
                                        0
                                        а проходит само проверку=)?
                                        +4
                                        Она проверяет валидность email по RFC. И, говорят, не 100% точно.
                                          +5
                                          Бля
                                        0
                                        ух, красота :)
                                        • НЛО прилетело и опубликовало эту надпись здесь
                                        +2
                                        Ну да, как я и подозревал. А регексп злой, да. Я не сразу понял, что мрачный кусок с цифрами в середине матчит айпишники, причём следит, чтобы не подсунули цифры больше 255.
                                          +6
                                          RFC соответствует только одна фигня и она есть только в перле: www.ex-parrot.com/pdw/Mail-RFC822-Address.html
                                            0
                                            Может здесь проверяются мыла только вида name@domain только.

                                            по RFC 822, ведь проходят ещё такие мыла:

                                            «user» <user@domain>
                                            И ещё такие.
                                            Wilt. (the Stilt) Chamberlain@NBA.US
                                            Cruisers: Port@Portugal, Jones@SEA;
                                            $@[]
                                            *()@[]
                                            «quoted ( brackets» ( a comment )@example.com
                                              +2
                                              /^((\"[^\"\f\n\r\t\b]+\")|([A-Za-z0-9_][A-Za-z0-9_\!\#\$\%\&\'\*\+\-\~\/\=\?\^\`\|\{\}]*(\.[A-Za-z0-9_\!\#\$\%\&\'\*\+\-\~\/\=\?\^\`\|\{\}]*)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9])(([A-Za-z0-9\-])*([A-Za-z0-9]))?(\.(?=[A-Za-z0-9\-]))?)+[A-Za-z]+))$/

                                              В начале зачем-то вставили поддержку кавычек, типа «mailbox»@example.org, причем в кавычки можно впихать что угодно.
                                              Без кавычек тоже забавные варианты имеются, типа _@example.org и _........@example.org
                                              Про IP тут уже говорили — вроде как умеет user@[1.2.3.4] так и user@1.2.3.4. Соответственно больше 255 нельзя вписать, но 0.0.0.0 и 255.255.255.255 или 239.0.0.1 такие же невалидные адреса.

                                              При этом регэксп недоделанный. Местами * вместо + стоит. Зачем-то повторили 8 раз комбинацию цифр для IP, когда можно было бы сделать 2 раза.
                                                0
                                                А чем плох адрес _@example.org?
                                                Если допускается в имени "_", то может быть имя из одного этого символа.

                                                Насчёт 255.255.255.255 не знаю… Не регулярское это дело, её дело проверять только форму записи.
                                                  0
                                                  Проблема не в том что регулярка пропускает такие IP адреса, проблема в том что их пропускает фильтр.

                                                  Адрес плох тем что он не существует. Я обычно требую хотя бы одну букво-цифру между знаками в имени пользователя. Была сделана попытка с точкой, но кривая.
                                                  По контрасту в имени домена таких проблем нет, видимо стырили.
                                            0
                                            ну вот хочу я, например, получить список всех ссылок на странице…
                                            через фильтры этого не сделать,
                                            либо делать html валидным xhtml и далее искать через xpath,
                                            либо старым проверенным способом — через регулярные выражения

                                            так что заголовок сильно отдает желтизной
                                              +2
                                              По моему речь идет о использованию регулярок в фильтрах а не вообще
                                                +3
                                                я обычно через DOM делаю, особых граблей (грабель… тьфу, да какая к черту разница, пятница сегодня) не встречал.
                                                  –1
                                                  если не сложно, хотелось бы краткую реализацию поиска по DOM с помощью PHP. спасибо :)
                                                    –2
                                                    rtfm
                                                      +3
                                                      $dom = new DOMDocument();
                                                      $dom->loadHTML( $html) ;
                                                      $anchors = $dom->getElementsByTagName("a");
                                                      foreach ($anchors as $anchor) {
                                                      	$href = $anchor->getAttribute("href");
                                                      	....
                                                      }
                                                      
                                                    0
                                                    ну что вы
                                                    вот здесь описывается как с помощью DOMDocument->getElementsByTagName() найти все ссылки те же. без задумывания о регулярках.

                                                    нехай компутер думает у него голова для этого больше заточена
                                                    0
                                                    А по теме, действительно, писать смерть регуляркам очень громко.
                                                    Даже в самой filter_var() есть параметр FILTER_VALIDATE_REGEXP
                                                      0
                                                      тоже сразу подумал про этот фильтр.
                                                      На самом деле довольно часто приходится его использовать при нестандартных запросах.
                                                      +10
                                                      Читайте в следующем номере:
                                                      Регулярки: смерть фильтрам и правильная валидация!
                                                      :-)
                                                        +4
                                                        «обезопасивающие»… Шедевр:)
                                                          +2
                                                          а почему не упомянули про filter_input? это как раз тоже важно
                                                            –5
                                                            [irony]PHP — смерть Си![/irony]
                                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                              • НЛО прилетело и опубликовало эту надпись здесь
                                                                +1
                                                                code.google.com/p/km53/source/browse/trunk/lib/km/util/Validator.php — ОО-обертка вокруг функций filter_*. Не претендует на всеобъемлющую реализацию, но удобно, ИМХО (ну, свой велосипед часто кажется удобным).
                                                                  +1
                                                                  Никуда не годные эти фильтры
                                                                  $var = "#mail@example.com";
                                                                  var_dump(filter_var($var, FILTER_VALIDATE_EMAIL));
                                                                  

                                                                  выводит false, а ведь "#mail@example.com" — корректный адрес электронной почты согласно RFC 3696
                                                                    0
                                                                    Там в регулярке что внутри сидит, первой должна быть буква-цифра-подчерк. Дальше по списку разрешенных симоволов.
                                                                    Либо имя юзера в кавычках ( не помню — так можно по RFC? )
                                                                    +3
                                                                    У меня уже есть готовый класс с набором статических методов, таких как Validator::isEmail($var); И если заказчик попросит дописать поддержку кирилических доменов, то я допишу. А с filter_var() я что делать буду?

                                                                    Кроме того в этих ПХП-фильтрах нет очень нужных фильтров: isDate(), isTime(), isDateTime(), isPhone(), isMoney()

                                                                    Если их ф-ция окажется лучше моей, то я могу в тело своей ф-ции вставить вызов filter_var(). А вот обратно — никак.
                                                                      0
                                                                      Дата и время, валюта — зависят от локали, страны и языка.
                                                                        +1
                                                                        1. Формат времени точно не зависит от локали. Везде «HH:mm:ss». Хотя секунд может не быть.
                                                                        2. Самый часто используемый формат — тот который в БД DATE: «ГГГГ-ММ-ДД». Любой DatePicker можно настроить. А сепаратор принимать в качестве аргумента. У меня Validator::isDate($var, $separator='-');
                                                                        3. php.net/manual/en/function.setlocale.php

                                                                        Короче, слабоваты эти встроенные ф-ции.
                                                                      +2
                                                                      >а может просто мануал плохо читали

                                                                      а может потому, что ман на эти фильтры Кэп, причем ленивый. Особенности работы не расписаны, мне вот проще самому написать, чем лезть в сорцы и парсить чужой бред.
                                                                      Задумка неплохая, но получился кривой черный ящик. Умилило примечание в мане:
                                                                      Numbers +0 and -0 are not valid integers but validate as floats

                                                                      Тот же банальный фильтр email, как правильно заметил Eugney, даже не соответствуют стандарту.

                                                                      Так что в печь такие фильтры.
                                                                      +1
                                                                      Может кому пригодится — список фильтров.
                                                                        +2
                                                                        Видел, пробовал, не использую.
                                                                          +1
                                                                          Хотел как-то написать подобный топик, но вы опередили. Молодец, что подняли тему.

                                                                          Ну и понятно, что регекспам не смерть: самописные функции и валидаторы включают произвольную логику, среди которой запросто могут затесаться регекспы :)
                                                                            0
                                                                            Исправили бы досадные описки, просто перечитайте свою статью и поймете:)
                                                                              0
                                                                              Уже краем глаза видел. Спасибо, как раз есть где опробовать)
                                                                                0
                                                                                Пытался использовать. Имхо — не удобно. Гораздо удобнее и приятнее собственные классы для валидации.
                                                                                А разбираться в чужом коде, как и писали выше, не всегда есть желание и время.
                                                                                  0
                                                                                  Ээ, встроенные функции расширения языка хуже самописного кода? Очень спорно. Равно как и забивать на мануал по языку.

                                                                                  Но понятно, что если нет валидации и фильтра под конкретный тип и формат данных — придется писать свои валидатор и фильтр. Но изобретать велосипеды — т.е. придумывать заново то, что уже написано до вас (без определенной оправдывающей цели с вашей стороны) — как-то нерационально.
                                                                                  0
                                                                                  Интересная фича, но наверное придумана больше — для легкого изучения пхп новичками. Ясное дело тот кто (хотябы пол-года пишет сайт) способен написать свою валидацию — не будет рисковать и использовать эту, т к понятно(точнее непонятно) в какие бока это может вылезти потом.
                                                                                  • НЛО прилетело и опубликовало эту надпись здесь
                                                                                      0
                                                                                      filter_* по-моему полезен не столько для проверки входных параметров на соответствие определенному RFC или узкому типу данных, сколько для отрезания html-тэгов (с потенциальным XSS) в тривиальном тексте. То есть, это такой шаг в сторону мысли «фильтровать данные из браузера — это хороший тон и все такое», а вовсе не про регулярные выражения. Хотя даже тут могут быть проблемы, конечно.

                                                                                      Если речь идет о фреймворках, которые позволяют вызывать методы кода в зависимости от браузерного запроса, то как раз там важно, чтобы аргументы методов фильтровались самим фреймворком, а разработчик был вынужден указывать по какому типу фильтра, что какбе намекнет ему, что без фильтров теперь не принято.
                                                                                        +5
                                                                                        ололо… очередной топик из серии «смотрите, я прочитал мануал!»…
                                                                                          0
                                                                                          почему такое пропускает?
                                                                                          var_dump(filter_var('http://aaa<>', FILTER_VALIDATE_URL)); //string(12) «aaa<>»
                                                                                            0
                                                                                            Смотрите RFC2068

                                                                                            unsafe = CTL | SP | <"> | "#" | "%" | "<" | ">"

                                                                                            Characters other than those in the "reserved" and "unsafe" sets (see
                                                                                            section 3.2) are equivalent to their ""%" HEX HEX" encodings.
                                                                                            0
                                                                                            Особенно напрягают «валидаторы» у которых не проходит email со знаком "+". типа «mail+sometext@example.com» чтоб проще выяснить откуда спам сыпется…
                                                                                              –1
                                                                                              Кстати, интересная идея :) У вас еще не набралась статистика тех, кто сливает или допускает слив email?
                                                                                              0
                                                                                              пробовал, в некоторых случаях удобно
                                                                                              но к сожалению, например, валидация по урлу как-то криво реализована, пользуюсь своей регуляркой
                                                                                              бывают достаточно тонкие моменты, где работать с «черным ящиком» в виде filter_validate не приемлемо
                                                                                                0
                                                                                                Юзаю уже несколько лет Zend Framework. Там реализованы и валидаторы, и фильтры, есть набор с десяток дефолтных плюс своих написано порядком уже.
                                                                                                  0
                                                                                                  Удобная штука, но в некоторых версиях PHP с багами.
                                                                                                  Например, не пропустит домен с дефисом.
                                                                                                  bugs.php.net/bug.php?id=51192
                                                                                                  bugs.php.net/bug.php?id=51632
                                                                                                    0
                                                                                                    Фильтры данных впервые появились в PHP 5.0, и по какому-то стечению обстоятельств остались незамечеными большй частью кодеров.

                                                                                                    Мануал по фильтрам находится здесь

                                                                                                    Вообще-то, по данной же ссылке документации PHP указывается, что все функции Data Filtering:
                                                                                                    PHP 5 >= 5.2.0
                                                                                                    а не 5.0

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

                                                                                                    Самое читаемое