Используя функции фильтра PHP, такие как filter_var_array(), есть ли способ проверить, меньше ли длина входной строки некоторого значения

Я играл с библиотекой фильтров PHP. Мне понравилось, но я не могу выполнить простую функцию фильтра. По сути, я хочу аннулировать те значения в моем входном массиве, которые являются строками и длиннее определенного значения. Есть ли способ сделать это, например,

$data = array('input_string_array' => array('aaa', 'abaa', 'abaca'));
$args = array(
    'component'    => array('filter'    => FILTER_DEFAULT,
                            'flags'     => FILTER_REQUIRE_ARRAY, 
                            'options'   => array('min_length' => 1, 'max_length' => 10)
                           )
);

var_dump(filter_var_array($data, $args));

Я попробовал это, и это дало мне ошибку. потому что, по-видимому, нет доступных опций min_length/max_length. Но тогда как это реализовать? Также есть место, где упоминаются все такие параметры, как max_range, min_range, regexp.

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

echo filter_var($string, FILTER_CALLBACK, array("options"=> array("lengthChecker", "5")));

Большое спасибо за помощь.


person Chantz    schedule 28.09.2009    source источник
comment
Я не могу найти фильтр, который бы делал то, что вы хотите, но вы, конечно, можете использовать диапазон от 100 до 999, если переменная должна иметь длину 3.   -  person Joost    schedule 29.09.2009
comment
Что ж, я пытался. Может мой ответ будет полезен, а может и нет. Но я даю его вам в надежде, что он может быть полезен (если не вам, то кому-то другому).   -  person Anthony Rutledge    schedule 23.02.2017


Ответы (4)


Если нет лучшего, более прямого фильтра+параметра, вы можете использовать FILTER_VALIDATE_REGEXP.

$data = array('input_string_array' => array('', 'aaa', 'abaa', 'abaca'));
$args = array(
  'input_string_array' => array(
    'filter' => FILTER_VALIDATE_REGEXP,
    'flags'     => FILTER_REQUIRE_ARRAY|FILTER_NULL_ON_FAILURE,
    'options'   => array('regexp'=>'/^.{1,3}$/')
  )
);
var_dump(filter_var_array($data, $args));

отпечатки

array(1) {
  ["input_string_array"]=>
  array(4) {
    [0]=>
    NULL
    [1]=>
    string(3) "aaa"
    [2]=>
    NULL
    [3]=>
    NULL
  }
}

Чтобы избавиться от элементов NULL, вы можете использовать, например. array_filter().

person VolkerK    schedule 28.09.2009
comment
Лучше? Более прямой? Вероятно, не в контексте filter_var_array(). Но, может быть, сработает анонимная функция? Возможно, нет. - person Anthony Rutledge; 23.02.2017

Я считаю, что исходная проблема заключалась в том, что поле в массиве данных не было названо так же, как поле в массиве фильтров.

person grantwparks    schedule 10.02.2011

Введение

Вам понадобится как минимум PHP 5.4+, чтобы этот ответ работал (как минимум, из-за синтаксиса массива).

Если цель состоит в том, чтобы сделать что-то вроде этого:

$filteredArray = filter_var_array($arrayToFilter, $filterInstuctionsArray);

... и использовать возвращаемые результаты базовых функций PHP (вместе с логикой принятия решений) для проверки длины (mb_strlen(), strlen()) строки, ключ в том, чтобы иметь отличные инструкции фильтра Array`. Чтобы передать аргументы вашей функции обратного вызова (для повторного использования, инкапсуляции, обобщения и т. д.), я считаю, что есть как минимум два сценария.

Сценарии

A) Формы класса/объекта.

'options' => [$this, 'callbackMethod']

'options' => [$this->object, 'callbackMethod']

'options' => [$object, 'callbackMethod']

B) процедурная форма.

'options' => 'callbackFunction'

Решения

1) Замените $this или $object в сценарии A на new экземпляр объекта прямо здесь и сейчас, передав в любых аргументах его конструктор. Это кажется маловероятным, так как попахивает тесно связанным кодом.

В качестве альтернативы можно внедрить предварительно заполненный объект в какой-нибудь Validator класс и запустить filter_input_array() из этого Validator класса. Таким образом, передача аргументов в callbackMethod не требуется.

Or

2) Замените 'callbackFunction' в сценарии B на анонимная функция PHP и реализовать синтаксис use для передачи ей аргументов/ограничений из класса Validator.

function ($value) use ($min, $max) {
    $length = mb_strlen($value, 'UTF-8');
    return ($length >= $min) && ($length <= $max);
}

Попытка №1: фильтрация массива инструкций с помощью анонимных функций

Вот последовательный пример работы со скалярными значениями.

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => function ($value) use ($min, $max) {
                                                          $length = mb_strlen($value, 'UTF-8');
                                                          return ($length >= $min) && ($length <= $max);}],
                            'lName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => function ($value) use ($min, $max) {
                                                          $length = mb_strlen($value, 'UTF-8');
                                                          return ($length >= $min) && ($length <= $max);}]
                         ];

Конечно, это нарушает принципы DRY. Таким образом, вы можете определить анонимную функцию как свойство класса Validator (или просто присвоить ее переменной), что сделает ее именованным экземпляром объекта Closure.

private $checkNameLength = function ($value) use ($this->nameMin, $this->nameMax) {
        $length = mb_strlen($value, 'UTF-8');
        return ($length >= $this->nameMin) && ($length <= $this->nameMax);
    };

Or

$checkNameLength = function ($value) use ($min, $max) {
        $length = mb_strlen($value, 'UTF-8');
        return ($length >= $min) && ($length <= $max);
    };

Итак, я надеюсь, что одно из двух сработает.

Попытка № 2: фильтрация массива инструкций с именованными анонимными функциями

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => [$this, 'checkNameLength']]
                         ];

Or

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => 'checkNameLength']
                         ];

Возможные осложнения

Экземпляры Closure $this->checkNameLength и $checkNameLength являются объектами, а не обычными "методами/функциями". Но, если PHP не жалуется, я не буду мудрить. Я полагаю, можно попробовать определить функцию внутри анонимной функции.

$checkName = function ($value) use ($min, $max) {

        function lengthTest($string, $min, $max){
            $length = mb_strlen($string, 'UTF-8');
            return ($length >= $min) && ($length <= $max);
        }
    };

Тогда ваш массив инструкций фильтра будет выглядеть так.

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => [$checkName, 'lengthTest']]
                         ];

Или, наверное, это...

$filterInstructionsArray[
                            'fName' = ['filter'  => FILTER_CALLBACK,
                                       'flags'   => FILTER_REQUIRE_SCALAR,
                                       'options' => 'lengthTest']
                         ];

Вывод

Существует лучший способ создать универсальную проверку длины строки, чем использование filter_var_array() и анонимных функций PHP. Использование 'filter' => FILTER_VALIDATE_REGEXP может упростить проверку длины отдельной строки, но не заменяет соблюдение принципов DRY. В конечном итоге у вас будет много инструкций в массиве инструкций фильтра, таких как this, просто для обработки длин строк в массиве ввода.

    //If you just want to test only the lengths first, this is
    //very inefficient. Assume each $regex is only checking string length.

    $filterLengthInstructions = [
                'fName'    => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $fNameRegex]],
                'lName'    => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $lNameRegex]],
                'company'  => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $comanyRegex]],
                'address1' => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $address1Regex]],
                'address2' => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $address2Regex]],
                'zip'      => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $zipRegex]],
                'website'  => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $urlRegex]],
                'email'  => ['filter'  => FILTER_VALIDATE_REGEXP,
                               'flags'   => FILTER_REQUIRE_SCALAR,
                               'options' => ['regexp' => $emailRegex]]
     ];

Это можно сделать, если вы пытаетесь создать чистый filter_input_array() Validator класс или подпрограмму. Но вам придется сделать несколько проходов filter_var_array() с несколькими массивами инструкций фильтра (поскольку длина строки — не единственное, что что делает его действительным или недействительным).

Для website и email вы захотите воспользоваться преимуществами 'filter' => FILTER_VALIDATE_URL и 'filter' => FILTER_VALIDATE_EMAIL. В другое время вы захотите воспользоваться преимуществом 'filter' => FILTER_VALIDATE_IP. Кроме того, особенно в случае email, вы можете захотеть ограничить действительные адреса электронной почты подмножеством официального RFC с регулярным выражением. Чтобы сохранить чистоту, вы не стали бы помещать это регулярное выражение в файл $filterLengthInstructions.

Фундаментальные причины желания изменить min_length и max_length — возможность один раз написать бизнес-логику и использовать ее везде.

Как минимум, я советую создать абстрактный суперкласс Validator и определить один (1) метод, который проверяет длину строк.

Проверка класса

Теперь вы можете разделить этот метод или делегировать эту задачу внедренному методу объекта StringTester.

Проверка класса с помощью StringTester

Как бы то ни было, но, определив конкретные дочерние классы Validator, все, что вам нужно сделать, это определить параметры теста в массиве, создать цикл и вызвать:

$this->testString($string, $min, $max, $pattern, $errorMessage);

or

$this->stringTester->testString($string, $min, $max, $pattern, $errorMessage);

... внутри цикла. Обязательно учитывайте ошибку $errorMessage.

abstract Class Tester
{

}

class StringTester extends Tester
{
    private function testString($string, $min, $max, $pattern, &$errorMessage)
    {
        $length = mb_strlen($string, 'UTF-8');

        if($length < $min)      //Test string against minimum length.
        {
            $errorMessage = 'Too small! ('.$min.' min, ' .$length. ' given.)';
        }
        elseif($length > $max)  //Test string against maximum length.
        {
            $errorMessage = 'Too large! ('.$max.' max, ' .$length. ' given.)';
        }
        elseif(preg_match($pattern, $string) === 0)  //Test string's pattern.
        {
            $errorMessage = 'Invalid string format!';
        }
        else
        {
            $errorMessage = '';  //The error message is the empty string.
        }

        return;
    }
}

abstract Class Validator
{
    //Arrays
    protected $inputArray;
    protected $errorMessagesArray = [];
    protected $stringTestRulesArray;  //I know. I know. :-)

    //Objects
    protected $stringTester;

    //Abstract functions
    abstract public function validate();

    public function __construct(Tester $stringTester, array $inputArray, array $stringTestRutlesArray)
    {
        $this->stringTester         = $stringTester;
        $this->inputArray           = $inputArray;
        $this->stringTestRulesArray = $stringTestRulesArray
    }

    public function getInput()
    {
        return $this->inputArray;
    }

    public function getErrorMessages()
    {
        return $this->errorMessagesArray();
    }

    protected function validateStrings()
    {
        //Notice how input values correspond to error message elements via $key.
        foreach($this->stringTestRulesArray as $key = $valuesArr)
        {
            $this->stringTester->testString($this->inputArray[$key], $valuesArr['min'], $valuesArr['max'], $valuesArr['pattern'], $this->errorMessagesArray[$key]);
        }

        return;
    }

}

class ContactValidator extends Validator
{
    public function __construct(Tester $stringTester, Sanitizer $sanitizer)
    {
        $stringTestRulesArray = [
                                  'fName' => ['min' => 1, 'max' => 25, 'pattern' => '/[A-Za-z\' -]/'],
                                  'lName' => ['min' => 1, 'max' => 40, 'pattern' => '/[A-Za-z\' -]/']
                                ];

        parent::__construct($stringTester, $sanitizer->getInput(), $stringTestRulesArray);
    }

    public function validate()
    {
        $this->validateStrings();
        //Other, contact form specific validation stuff.
    }
}

class RegisterValidator extends Validator
{
    public function __construct(Tester $stringTester, Sanitizer $sanitizer)
    {
        $stringTestRulesArray = [
                                  'fName' => ['min' => 1, 'max' => 30, 'pattern' => '/[A-Za-z\' -]/'],
                                  'lName' => ['min' => 1, 'max' => 45, 'pattern' => '/[A-Za-z\' -]/']
                                ];

        parent::__construct($stringTester, $sanitizer->getInput(), $stringTestRulesArray);
    }

    public function validate()
    {
        $this->validateStrings();
        //Other, register form specific validation stuff.
    }
}
person Anthony Rutledge    schedule 23.02.2017
comment
Примечание. Это не полное решение, так как в формах есть необязательные поля, содержащие и пустые строки (текстовые поля, текстовые области и т. д.), а также переходные поля, которые не регистрируются. с сервером, если они не отмечены (флажками). Кроме того, запись результатов теста в $testResultsArray позволяет разумно устанавливать значения CSS на основе логического значения (true или false). Однако у меня есть это полное решение на моем компьютере. - person Anthony Rutledge; 23.02.2017
comment
Кроме того, как насчет объектов Tester для целых чисел и чисел с плавающей запятой. - person Anthony Rutledge; 23.02.2017
comment
Наконец, существует вероятность того, что строка действительна только в том случае, если она найдена в наборе возможных значений (списки выбора и т. д.). Мое решение также охватывает это. - person Anthony Rutledge; 23.02.2017

Что касается второй части вопроса, касающегося FILTER_CALLBACK.

В PHP 5.4+ вы можете создать функцию, которая возвращает статическую функцию, которая будет действовать как обратный вызов.

Пример https://3v4l.org/m3isD

Я включаю пример функции validate_length, не стесняйтесь использовать свою собственную с любой желаемой санитарией или проверкой. Например, вызов filter_var($value, $options);

function validate_length($min = 1, $max = null) 
{
    return static function ($value = null) use ($min, $max) {
        $len = strlen($value);
        if (null !== $min && $len < $min) {
           return false;
        }
        if (null !== $max && $len > $max) {
           return false;
        }

        return $value;
    };
}

$_POST = [
   'a' => 'value',
   'b' => '',
   'c' => 'Valid'
];

$result = filter_input_array(INPUT_POST, [
    'a' => ['filter' => FILTER_CALLBACK, 'options' => validate_length(null, 4)],
    'b' => ['filter' => FILTER_CALLBACK, 'options' => validate_length(1)],
    'c' => ['filter' => FILTER_CALLBACK, 'options' => validate_length(1, 10)]
]);

var_dump($result);

Результат

array(3) {
  ["a"]=>
  bool(false)
  ["b"]=>
  bool(false)
  ["c"]=>
  string(5) "Valid"
}

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

Пример https://3v4l.org/83uXu

function validate_length($options = ['min_length' => 1, 'max_length' => null], $filters = null)
{
    return static function ($value = null) use ($options, $filters) {
        $len = strlen($value);
        if (isset($options['min_length']) && $len < $options['min_length']) {
           return false;
        }
        if (isset($options['max_length']) && $len > $options['max_length']) {
           return false;
        }
        if (null !== $filters) {
            return filter_var($value, ...$filters); //php 5.6+ array unpacking operator
        }

        return $value;
    };
}

$a = [
   'a' => 'value',
   'b' => '',
   'c' => '[email protected]',
   'd' => 'This& is a ???? Test',
];

$email_filter = [FILTER_VALIDATE_EMAIL, ['flags' => FILTER_FLAG_EMAIL_UNICODE]];
$string_filter = [FILTER_SANITIZE_STRING, ['flags' => FILTER_FLAG_ENCODE_AMP | FILTER_FLAG_STRIP_HIGH]];

$result = filter_var_array($a, [
    'a' => ['filter' => FILTER_CALLBACK, 'options' => validate_length(['max_length' => 4])], 
    'b' => ['filter' => FILTER_CALLBACK, 'options' => validate_length(['min_length' => 1])], 
    'c' => ['filter' => FILTER_CALLBACK, 'options' => validate_length(['min_length' => 1, 'max_length' => 20], $email_filter)],
    'd' => ['filter' => FILTER_CALLBACK, 'options' => validate_length(['min_length' => 1], $string_filter)],
]);

var_dump($result);

Результат

array(4) {
  ["a"]=>
  bool(false)
  ["b"]=>
  bool(false)
  ["c"]=>
  string(13) "[email protected]"
  ["d"]=>
  string(20) "This&#38; is a  Test"
}
person Will B.    schedule 09.07.2020