webew
Войти » Регистрация
 
PHP

PHP: инструмент для проверки правильности заполнения веб-форм

9 августа 2013, 19:14

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

Средство реализовано в виде функции, которая в качестве аргументов принимает массив с настройками проверок и массивы с данными запросов (обычно $_GET или $_POST), а в результате работы возвращает одномерный массив строк с сообщениями об ошибках. Код функции можно скачать здесь.

Каждый элемент массива с настройками соответствует ключу запроса (читай полю формы), и в нём перечисляются инструкции, по которым следует выполнять проверки.

Базовые проверки

Рассмотрим в качестве примера форму для заполнения полей почтового отправления, куда пользователь обязательно должен ввести наименование получателя, почтовый адрес, индекс и вес посылки, а телефон и адрес электронной почты - по желанию, но при этом последний нужно проверить на правильность.

<form>
    Получатель* <input name="fio">
    Адрес* <input name="address">
    Почтовый индекс* <input name="postcode">
    Вес отправления, кг* <input name="weight">
    Телефон <input name="phone">
    E-mail <input name="email">
    <button>Отправить</button>
</form>

Вот как будет выглядеть код для выполнения проверки:

$check_cfg = array(
        'fio' => 'Укажите получателя отправления.',
        'address' => array(
                '*' => 'Укажите адрес.',
            ),
        'postcode' => array(
                '*' => 'Укажите почтовый индекс места назначения.',
                '/\d{6}/' => 'Почтовый индекс должен состоять из шести цифр.',
            ),
        'weight' => array(
                '*' => 'Укажите примерный вес отправления.',
                '>= 0.2' => 'Минимальный регистрируемый вес отправления — 0.2 кг',
                '< 10' => 'Отправления тяжелее 10 кг не принимаются.',
            ),
        'email' => array(
                '/^\w[\w.-]*@(\w+\.)+\w{2,6}$/' => 'Адрес электронной почты введён некорректно.'
            ),
    );
   
$errors = check_form_errors($check_cfg, $_GET)

Как уже было сказано выше, каждому из полей формы соответствует набор инструкций, последовательно применяемых для выполнения проверок. Разберём каждую из них:

'*' => 'текст' - означает "проверить, что поле присутствует в запросе и заполнено (имеет ненулевую длину без учета пробелов по краям), в противном случае использовать 'текст' в качестве сообщения об ошибке". Для поля поля address эта проверка является единственной, и набор инструкция для него вместо

'address' => array(
        '*' => 'Укажите адрес.',
    )

можно записать в сокращенной форме: 'address' => 'Укажите адрес.', как это сделано для поля fio.

Инструкция вида '/.../' => 'текст' (ключ начинается и заканчивается слэшом) приводит к проверке поля регулярным выражением, содержащимся в ключе, в случае несовпадения с которым 'текст' будет использован в качестве сообщения об ошибке. В регулярном выражении можно использовать модификаторы, поместив их после закрывающего слэша.

Инструкции, где в ключе сначала идёт знак какого-либо неравенства, обрабатываются буквально так: «применить неравенство точно в указанном виде, подставив слева значение поля; если неравенство неверно — использовать текст в качестве сообщения об ошибке»1.

Окружающие пробелы удаляются из содержимого полей2 до начала выполнения проверок, так что учитывать их возможное присутствие при составлении инструкций не нужно.

Важно отметить, что все инструкции, кроме '*', выполняются только когда поле заполнено3. Поэтому, если отправить вышеуказанную форму, не заполнив ни одно поле, то, например, для почтового индекса и для веса сработают только первые из сообщений об ошибке, а для email ошибок вообще не будет.

Проверки с помощью сторонних функций

Проверки можно проводить и с помощью функций. Такие проверки могут быть двух типов.

Первый тип реализуется на основе функций, возвращаещих в результате работы логическую величину (TRUE или FALSE). В случае FALSE считается, что проверка не пройдена, и задействуется сообщение об ошибке.

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

Рассмотрим оба этих типа на примерах.

Предположим, решено проверять также номер телефона, но использовать для этого одно регулярное выражение неудобно, и лучше написать для проверки специальную функцию — скажем, validate_phone() — которая в качестве аргумента принимает номер телефона, а в ответ возвращает, правильный телефон или нет. Вот как будут выглядеть инструкции для поля phone в таком случае:

'phone' => array(
        'validate_phone()' => 'Номер телефона указан некорректно. Проверьте правильность написания.'
    );

При такой форме записи функции в качестве единственного аргумента передаётся значение соответствующего поля.

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

'phone' => array(
        'validate_phone()' => array(
                'text' => 'Номер телефона указан некорректно. Проверьте правильность написания.',
                'args' => array('{*value*}', '495', $some_var),
            )
    )

Вообще говоря, полная запись любой инструкции включает в себя элемент 'text'. Например, проверка на заполнение поля fio в развернутом виде выглядит так:

'fio' => array(
        '*' => array(
                'text' => 'Укажите получателя отправления.'
            ),
    )

Если инструкция записана в таком, развернутом, виде, и элемент 'text' в ней отсутствует, проверка не приводит к регстрации ошибки ни в каком случае, независимо от своего результата. (Это свойство важно для некоторых случаев, речь о которых пойдет далее).

{*value*} — специальная метка, вместо которой будет подставлено значение поля. Её можно использовать и в текстах ошибок (это касается любых проверок, не только на основе функций). К примеру:

'email' => array(
        '/^\w[\w.-]*@(\w+\.)+\w{2,6}$/' => 'Введённый адрес e-mail — {*value*} — некорректен.'
    )

Инструкции, включающие второй тип проверок, сходны по виду и отличаются лишь наличием знака '+' перед именем функции.

Допустим, для проверки строки адреса стали использовать функцию find_address_errors(), которая в данной ей адресной строке ищет разнообразные ошибки и возвращает их в виде строки или списка (массива строк). Инструкции для поля address в этом случае станут выглядеть так:

'address' => array(
        '*' => 'Укажите адрес.', // Эта проверка остается без изменений
        '+find_address_errors()' => array(), // тексты ошибок находятся в коде функции
    )

Дополнительные аргументы, при необходимости, следует передавать так же, как и для первого типа инструкций.

Альтернативный вариант провести проверку второго типа — определить функцию прямо тут же:

'address' => array(
        '*' => 'Укажите адрес.',
        function($query, $field_name) {
            // Этой функции передается весь массив с данными запроса ($_GET или $_POST)
            // а также название поля, для которого идет проверка.
            $value = $query[$field_name]; // Так можно получить значение поля
            ...
            // В случае обнаружения ошибок нужно вернуть их тексты
            // в виде строки или массива строк
        }
    )

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

Вложенные проверки

Бывают случаи, когда содержимое одного поля влияет на требования, предъявляемые к какому-то другому полю. Рассмотрим такую ситуацию на примере.

Скажем, при вводе параметров почтового отправления пользователям по желанию предоставляется возможность где-то зарегистрироваться. Изъявить такое желание можно, заполнив необязательное поле для пароля (<input name="password" ...>), при этом в качестве логина будет использован email; если пароль будет указан, а email — нет, это следует воспринимать как ошибку.

Это вносит в логику проверки некоторые изменения. Набор обязательных полей нужно в общем случае оставить неизменным, но если заполнено поле password — провести некоторые дополнительные проверки. Вот как в таком случае будут выглядеть инструкции для поля password:

'password' => array(
        '*' => array(
                '>>' => array(
                        'email' => 'Для регистрации введите адрес e-mail. (Если не хотите регистрироваться — просто оставьте поле "Пароль" пустым.)',
                    )
            ),
    )

Рассмотрим этот код подробнее.

Корневая инструкция представляет собой обычную проверку на заполненность. Однако она записана в развернутом виде и при этом элемент 'text' у этой инструкции отсутствует. Поэтому если эта проверка и потерпит неудачу (как это случится при отправке пустого пароля), никакой ошибки все равно зарегистрировано не будет.

Зачем же вообще тогда нужна такая инструкция? Ради элемента '>>'. Он представляет собой обычный список инструкций (и в него можно включать любые поля), которые выполняются только в том случае, если соответствующая корневая проверка пройдёт успешно (то есть, этот список является антагонистом ошибки). Буквально происходит следующее: те же самые поля независимо проверяются дополнительно с использованием массива '>>' в качестве набора инструкций, а выявленные ошибки дописываются к уже имеющимся4.

Предположим теперь, что в случае ввода пароля нужно не только проверить email, но и установить ограничение на длину пароля. Например, чтобы он был не короче трёх символов.

Это ограничение легко выразить с помощью регулярного выражения: '/.{3,}/'. Иметь эту инструкцию корневой, однако, нельзя (это обязало бы вводить пароль). Выйти из положения можно, поместив её в компанию к вложенной проверке email:

'password' => array(
        '*' => array(
                '>>' => array(
                        'email' => 'Для регистрации введите адрес e-mail. ...',
                        'password' => array(
                                '/.{3,}/' => 'Пароль должен быть не короче трёх символов.',
                            ),
                    )
            )
    )

Получается, что среди вложенных инструкций для поля password есть таковые для него самого же. Чтобы не писать название поля повторно, есть специальный ключ '/^R/'. С его помощью ту же самую инструкцию можно записать так:

'password' => array(
        '*' => array(
                '>>' => array(
                        'email' => 'Для регистрации введите адрес e-mail. ...',
                        '^R' => array( // указывает на password
                                '/.{3,}/' => 'Пароль должен быть не короче трёх символов.',
                            ),
                    )
            )
    )

Проверка файлов

Для файловых полей используются те же самые проверки, что и для обычных, однако они применяются уже не к собственно содержимому поля, а к массиву $_FILES, поэтому инструкции для файловых полей должны иметь специальную структуру.

Рассмотрим в качестве примера следующую HTML-форму:

<form method="POST" enctype="multipart/form-data">
    ...
    <input type="file" name="photo">
    ...
</form>

Массив $_FILES для такой формы в случае успешной загрузки файла будет иметь примерно вот такой вид:

Array
(
    [photo] => Array
        (
            [name] => britney 100x100.jpg
            [type] => image/jpeg
            [tmp_name] => /tmp/php1AD.tmp
            [error] => 0
            [size] => 2752
        )

)

Предположим, нужно проверить, чтобы загруженный файл являлся изображением в формате JPEG или PNG и не превышал по размеру 1 Мб. Код для таких проверок будет выглядить следующим образом (инструкции поля weight приводятся для сравнения):

$check_cfg = array(
        'photo' => array(
                'name' => array(
                        '/\.(jpe?g|png)$/i' => 'Файл должен быть в формате JPEG или GIF.',
                        // Для такой проверки можно было использовать и type:
                        // принципиальной разницы нет,
                        // т.к. type генерируется сервером на основе расширения файла.
                    ),
                'size' => array(
                        '< ' . (1024 * 1024) => 'Файл слишком большой, максимум - 1 Мб.',
                    ),
            ),
        'weight' => array(
                '*' => 'Укажите примерный вес отправления.',
                '>= 0.2' => 'Минимальный регистрируемый вес отправления — 0.2 кг',
                '< 10' => 'Отправления тяжелее 10 кг не принимаются.',
            )
    );

$errors = check_form_errors($check_cfg, $_POST, $_FILES)

Чтобы сделать загрузку файла обязательной, в список инструкций нужно добавить элемент '*':

'photo' => array(
        '*' => 'Нужно загрузить файл.',
        'name' => array(
                '/\.(jpe?g|png)$/i' => 'Файл должен быть в формате JPEG или PNG.',
                // Для такой проверки можно было использовать и type
                // принципиальной разницы нет, т.к. элемент type
                // генерируется сервером на основе расширения файла.
            ),
        'size' => array(
                '< ' . (1024 * 1024) => 'Файл {*name*} слишком большой, максимум - 1 Мб.',
                // Метка {*name*} заменится на исходное имя загруженного файла  
            ),
    )

Если проверка на загрузку как таковую является единственной, инструкцию для поля можно записывать в сокращенной форме:

'photo' => 'Нужно загрузить файл.'

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

Отдельного рассмотрения заслуживает детальная обработка ошибок состояния загрузки файла. Информация о состоянии загрузки содержится в элементе 'error' в виде числового кода. Набор этих кодов фиксирован и довольно невелик, поэтому в функцию включены стандартные сообщения об ошибках для некоторых из этих случаев:

  • UPLOAD_ERR_NO_FILE (4): "Не выбран файл для загрузки."
  • UPLOAD_ERR_INI_SIZE (1): "Размер файла {*name*} не должен превышать Vб" (где V = ini_get('upload_max_filesize'))
  • Остальные: "При загрузке файла {*name*} возникли технические проблемы (код ошибки: {*error*})."

Если сообщения, относящиеся к тем или иным ошибкам загрузки, требуется настроить, в инструкции проверок нужно включить массив 'error'5:

'photo' => array(
        'error' => array(
                UPLOAD_ERR_NO_FILE => 'Все-таки нужно загрузить файл.',
                UPLOAD_ERR_CANT_WRITE => 'Файл загрузить не получилось — наверное, на сервере кончилось место.',
                // Коды, для которых тексты сообщений об ошибках не указаны,
                // будут обработаны с использованием стандартных сообщений
            ),
        ...
    )

Если стандартные сообщения хочется использовать для всех ошибок загрузки, включая отсутствие файла, вместо инструкции '*' следует указать 'error' => TRUE.

Следует отметить, что если при загрузке файла возникли какие-либо сбои (код ошибки отличается от UPLOAD_ERR_OK), никакие инструкции, кроме собственно проверки состояния загрузки ('*' и 'error'), не исполняются.

Проверка изображений

Если предполагается загрузка изображения, можно выполнить ряд проверок некоторых его свойств, используя для этого специальный тип инструкций файлового поля. Это обычные инструкции, которые будут анализировать данные, полученные с помощью функции getimagesize(). В результате своей работы она возвращает массив следующего вида:

Array
(
    [0] => 450
    [1] => 675
    [2] => 2
    [3] => width="450" height="675"
    [bits] => 8
    [channels] => 3
    [mime] => image/jpeg
)

Предположим, нужно убедиться, что изображение нужного типа, и, кроме того, ограничить его максимальные размеры форматом 2048х1536. Также

Чтобы выполнить все эти проверки, нужно включить в инструкции для файлового поля специальный элемент — 'image':

'photo' => array(
        'image' => array(
                '*' => 'Файл "{*name*}" не является изображением или поврежден.',
                'mime' => array(
                        '/jpeg|png/' => 'Изображение должно быть в формате JPEG или PNG.'
                        // Нужно сказать, что getimagesize() устанавливает mime-type файла
                        // на основе анализа его содержимого (определяется верно даже
                        // при неправильном расширении), поэтому для проверки типа изображения
                        // лучше использовать результат работы getimagesize(),
                        // а не содержимое $_FILES.
                    ),
                'width' => array(
                        '<= 2048' => 'Ширина изображения не должна превышать 2048 px.',
                    ),
                'height' => array(
                        '<= 1536' => 'Высота изображения не должна превышать 1536 px.',
                    ),
            ),
    )

Инструкции по остальным свойствам (например, 'bits' или 'channels'), при необходимости, составляются по тем же принципам.

Ключи 'width' и 'height' используются в качестве псевдонимов для ключей 0 и 1 исходного массива от getimagesize().

Примечания

Дубликаты сообщений об ошибках перед завершением работы функции удаляются (с помощью array_unique).

Если содержимое поля формы является массивом, указанные проверки будут применены к каждому из его элементов.

Для генерации HTML-кода форм разработан специальный инструментарий.

1. Такие проверки лучше составлять, сначала продумав допустимый диапазон величин поля и выразив его в виде неравенства, а затем составить текст сообщения для случая, когда величина выходит за границы этого диапазона. Как правило, неравенство и текст должны противоречить друг другу (как это имеет место в примере: "<= 10" — "... тяжелее 10 кг ...", и т.п.).

2. C помощью функции trim().

3. При этом позиция, на которой среди инструкций находится '*', значения не имеет (то есть, её можно ставить и не первой, хотя это будет менее наглядно).

4. Никаких ограничений на вложенные инструкции по сравнению с корневыми нет. Уровень вложенности не ограничен.

5. Вообще говоря, инструкция '*' => 'текст' для файлового поля равносильна инструкции

'error' => array(
        UPLOAD_ERR_NO_FILE => 'текст'
    );


с той лишь разницей, что '*', в отличие от 'error', сработает и в том случае, если поле вообще отсутствует в форме (и — как следствие — отсутствует соответствующий элемент в массиве $_FILES).


© Все права на данную статью принадлежат порталу webew.ru. Перепечатка в интернет-изданиях разрешается только с указанием автора и прямой ссылки на оригинальную статью. Перепечатка в печатных изданиях допускается только с разрешения редакции.
Добавить комментарий

1234ru

В версии 1.1 добавлена возможность использовать для проверок безымянные функции, определяемые прямо в конфигурационном массиве.
То, что не убивает нас, делает нас инвалидами.
05.06.2015, 15:42
Ответить
© 2008—2017 webew.ru, связаться: x собака webew.ru
Сайт использует Flede и соответствует стандартам WAI-WCAG 1.0 на уровне A.
Rambler's Top100