Можно ли программно прочитать отсутствующие ошибки разделителя в регулярном выражении preg/PHP?

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

Существует гораздо более простой подход выполнение сопоставления и проверка на наличие ошибок, который возвращает правильный логический результат, но это было бы интересно также получить причину/сообщение о сбое:

// The error is that preg delimiters are missing
$testRegex = 'Location: (.+)';

// This bit is fine
$result = preg_match($testRegex, ''); // returns false i.e. failure
$valid = is_int($result); // false, i.e. the regex is invalid

// Returns PREG_NO_ERROR, which means no error occured
echo preg_last_error() . "\n";

Если я запускаю это, я правильно получаю:

Предупреждение PHP: preg_match(): разделитель не должен быть буквенно-цифровым или обратной косой чертой в ... в строке ...

Однако выход функции ошибки равен 0, что равно PREG_NO_ERROR. Я бы подумал, что это вернет ненулевой код ошибки - и было бы еще лучше, если бы я мог получить чистую версию предупреждающего сообщения.

Конечно, возможно, что это не общедоступно (т.е. доступно только для механизма PHP для печати предупреждения). Я бегу 5.5.3-1ubuntu2.6 (cli).


person halfer    schedule 02.05.2015    source источник
comment
Создайте свой собственный обработчик ошибок, возможно ли это?   -  person Rizier123    schedule 02.05.2015
comment
Я предполагаю, что preg_last_error ничего не возвращает, потому что в этом конкретном случае шаблон регулярного выражения даже не попал в механизм PCRE. Разделители шаблонов обязательны для PHP, а сама PCRE их не ожидает. PHP удаляет разделители из шаблона и передает это в PCRE. Попробуйте использовать недопустимый шаблон, но с разделителями, и посмотрите, возвращает ли preg_last_error что-то осмысленное.   -  person Lucas Trzesniewski    schedule 02.05.2015
comment
@ Лукас, спасибо - это звучит как точное изложение того, что происходит.   -  person halfer    schedule 02.05.2015
comment
Я бы попросил пользователя ввести только regex (без разделителей, отдельные поля ввода для флагов), затем объединить их в код и создать окончательную строку для передачи preg_match() и друзьям. Что-то вроде: '/'.preg_quote($_GET['regex']).'/'.$modifiers. Посетите regex101.com для вдохновения.   -  person axiac    schedule 02.05.2015
comment
@axiac Не забудьте указать разделитель в вызове preg_quote(). Я тоже их все время забываю :preg_quote($_GET["regex"], "/")   -  person Rizier123    schedule 02.05.2015
comment
^ @axiac, спасибо, это стоит рассмотреть. Единственная дилемма заключается в том, что если регулярное выражение предназначено, скажем, для URL-адреса, то фиксированная косая черта будет означать, что все буквальные косые черты нужно экранировать. Если это можно переключать в каждом конкретном случае, например. на ~~, то этой проблемы можно избежать.   -  person halfer    schedule 02.05.2015
comment
@halfer Итак, на ваш вопрос дан ответ или вы все еще ищете что-то другое?   -  person Rizier123    schedule 02.05.2015
comment
@ Rizier123 ты прав. Спасибо, что упомянули об этом.   -  person axiac    schedule 02.05.2015
comment
@ Rizier123, все ответили, спасибо! Ваш ответ и ответ Ивана были очень полезными.   -  person halfer    schedule 02.05.2015


Ответы (2)


Это должно сработать для вас:

Здесь я просто включаю буферизацию вывода с помощью ob_start(). Затем я фиксирую последнюю ошибку с помощью error_get_last(). Затем я заканчиваю буферизацию вывода с помощью ob_end_clean(). После этого вы можете проверить, пуст ли массив и не произошла ли ошибка.

ob_start();
$result = preg_match(".*", "Location: xy");
$error = error_get_last();
ob_end_clean();

if(!empty($error))
    echo "<b>Error:</b> " . $error["message"];
else
    echo "no error found!";

выход:

Error: preg_match(): No ending delimiter '.' found

ИЗМЕНИТЬ:

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

Затем вы можете поместить свой код в блок try - catch и поймать исключение.

set_error_handler(function($errno, $errstr, $errfile, $errline, array $errcontext) {
    // error was suppressed with the @-operator
    if (0 === error_reporting()) {
        return false;
    }
    throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});

try {
    $result = preg_match(".*", "Location: xy");
} catch(Exception $e) {
    echo "<b>Error:</b> " . $e->getMessage();
}

Код обработчика ошибок взят от Филиппа Гербера в этом ответ

person Rizier123    schedule 02.05.2015
comment
Очень креативное решение, спасибо! Оказывается, есть функция PHP, чтобы сделать это напрямую - см. Ответ Ивана. Не знаю, как я не знал об этом... :-) - person halfer; 02.05.2015
comment
@halfer Подойдет ли обработчик ошибок, потому что тогда вы сможете сделать это намного проще? - person Rizier123; 02.05.2015
comment
@halfer Оказывается, есть функция PHP, которая делает это напрямую Если вы имеете в виду error_get_last()? Я использую то же самое в своем коде ^. - person Rizier123; 02.05.2015
comment
Ой, я невнимательно прочитал ваш ответ! Извините, я сегодня не выпил достаточно кофе. В этом случае вам не нужна буферизация вывода — просто используйте @ в функции preg. - person halfer; 02.05.2015
comment
@halfer Но я бы не рекомендовал вам его использовать! ^ Как было сказано выше, у вас все в порядке с вашим собственным обработчиком ошибок, тогда вы можете сделать это намного чище, чем мой текущий ответ ?! - person Rizier123; 02.05.2015
comment
Я не знаю, что вы имеете в виду под моим собственным обработчиком ошибок, но я полагаю, что это будет хорошо. В чем проблема с подавлением ошибок здесь? Я очень хорошо понимаю причину, по которой его не следует использовать вообще, но в данном случае это явно обрабатывается. - person halfer; 02.05.2015
comment
@halfer Смотрите мой обновленный ответ, он может быть немного чище, если вы хотите использовать свой собственный обработчик ошибок. - person Rizier123; 02.05.2015
comment
Обычно это плохая практика, когда она используется для вещей, которые можно обработать с помощью обычного кода, например, неопределенный индекс при доступе к массиву. Однако многие функции PHP автоматически выдают подобную ошибку, которая действительно должна быть исключением. В этом случае можно использовать глушитель (@) и собрать информацию error_get_last(). - person Ivan Batić; 02.05.2015

Возможно, вы могли бы использовать error_get_last(), чтобы получить немного больше информации.

Array
(
    [type] => 2
    [message] => preg_match(): Delimiter must not be alphanumeric or backslash
    [file] => /Users/ivan/Desktop/test.php
    [line] => 6
)

type — это E_WARNING, и вы можете смело считать строку имени функции из части message, поскольку она всегда будет иметь один и тот же формат.

Затем вы можете сделать

$lastError = error_get_last()['message']; // php 5.5 expression
if(strpos($lastError, 'preg_match(): ') === 0){
    $error = substr($lastError, 14);
}

И $error будет var_dump переведено на

string(47) "Delimiter must not be alphanumeric or backslash"

Or a null

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

person Ivan Batić    schedule 02.05.2015
comment
Как получилось, что я использую PHP около десяти лет и не знаю об этой функции? Действительно очень удобно - и в данном случае работает очень хорошо. Спасибо! - person halfer; 02.05.2015
comment
Это случается со всеми, я рад, что смог помочь. - person Ivan Batić; 02.05.2015