PHP preg_match_all limit - PullRequest
       21

PHP preg_match_all limit

3 голосов
/ 25 ноября 2011

Я использую preg_match_all для очень длинного паттерна.

при запуске кода я получил эту ошибку:

Предупреждение: preg_match_all (): ошибка компиляции: слишком большое регулярное выражение со смещением 707830

После поиска я получил решение, поэтому мне нужно увеличить значение pcre.backtrack_limit и pcre.recursion_limit в php.ini

Но после того, как я увеличил значение и перезапустил свой apache, он все равно получил ту же проблему. Моя версия PHP 5.3.8

Ответы [ 3 ]

12 голосов
/ 25 ноября 2011

Эта ошибка не о производительности регулярного выражения, а о самом регулярном выражении. Изменение pcre.backtrack_limit и pcre.recursion_limit не будет иметь никакого эффекта, потому что регулярное выражение никогда не получает шанс на запуск. Проблема в том, что регулярное выражение слишком велико, и решение состоит в том, чтобы уменьшить регулярное выражение - намного, намного меньше.

7 голосов
/ 25 ноября 2011

увеличение PCRE-возврата и предела рекурсии может решить проблему, но все равно не удастся, когда размер ваших данных достигнет нового предела.(плохо масштабируется с большим количеством данных)

пример:

<?php 
// essential for huge PCREs
ini_set("pcre.backtrack_limit", "23001337");
ini_set("pcre.recursion_limit", "23001337");
// imagine your PCRE here...
?>

, чтобы действительно решить основную проблему, вы должны оптимизировать свое выражение и (если возможно) разбить ваше сложное выражение на "части "и переместить некоторую логику в PHP.Я надеюсь, что вы поймете идею, прочитав пример ... вместо того, чтобы пытаться найти подструктуру непосредственно с помощью одного PCRE, я продемонстрирую более «итеративный» подход, все глубже и глубже проникающий в структуру с использованием PHP.пример:

<?php
$html = file_get_contents("huge_input.html");

// first find all tables, and work on those later
$res = preg_match_all("!<table.*>(?P<content>.*)</table>!isU", $html, $table_matches);

if ($res) foreach($table_matches['content'] as $table_match) {  

    // now find all cells in each table that was found earlier ..
    $res = preg_match_all("!<td.*>(?P<content>.*)</td>!isU", $table_match, $cell_matches);

    if ($res) foreach($cell_matches['content'] as $cell_match) {

        // imagine going deeper and deeper into the structure here...
        echo "found a table cell! content: ", $cell_match;

    }    
}
3 голосов
/ 24 февраля 2016

Я пишу этот ответ, потому что наткнулся на ту же проблему.Как указал Алан Мур , настройка пределов возврата и рекурсии не поможет решить проблему.

Описанная ошибка возникает, когда игла превышает максимально возможный размер иглы, который ограниченбазовой библиотекой pcre.Описанная ошибка NOT вызвана php, но основной библиотекой pcre.Это сообщение об ошибке № 20, которое определено здесь:

https://github.com/php/.../pcre_compile.c#L477

php просто печатает текст ошибки, полученный из библиотеки pcre при ошибке.

Однако эта ошибкапоявляется в моей среде, когда я пытаюсь использовать ранее захваченные фрагменты в качестве иглы, и они имеют размер больше 32 Кбайт.

Это можно легко проверить с помощью этого простого скрипта из cli php

<?php
// This script demonstrates the above error and dumps an info
// when the needle is too long or with 64k iterations.

$expand=$needle="_^b_";
while( ! preg_match( $needle, "Stack Exchange Demo Text" ) )
{
    // Die after 64 kbytes of accumulated chunk needle
    // Adjust to 32k for a better illustration
    if ( strlen($expand) > 1024*64 ) die();

    if ( $expand == "_^b_" ) $expand = "";
    $expand .= "a";
    $needle = '_^'.$needle.'_ism';

    echo strlen($needle)."\n";

}
?>

Чтобы исправить ошибку, нужно уменьшить результирующую иглу или - если необходимо захватить все - использовать несколько preg_match с дополнительным параметром offset .

<?php
    if ( 
        preg_match( 
            '/'.preg_quote( 
                    substr( $big_chunk, 0, 20*1024 ) // 1st 20k chars
                ) 
                .'.*?'. 
                preg_quote( 
                    substr( $big_chunk, -5 ) // last 5
                ) 
            .'/', 
            $subject 
        ) 
    ) { 
        // do stuff
    }

    // The match all needles in text attempt
    if ( preg_match( 
            $needle_of_1st_32kbytes_chunk, 
            $subj, $matches, $flags = 0, 
            $offset = 320 // Offset -> 0
        )
        && preg_match( 
            $needle_of_2nd_32kbytes_chunk, 
            $subj, $matches, $flags = 0, 
            $offset = 32*1024*1 // Offset -> 32k
        )
        // && ... as many preg matches as needed
    ) {
        // do stuff
    }

    // it would be nicer to put the texts in a foreach-loop iterating
    // over the existings chunks 
?>

Вы поняли. *1024*

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

...