Thay thế bổ ngữ preg_replace () e bằng preg_replace_callback


83

Tôi rất tệ với các biểu thức chính quy. Tôi đang cố gắng thay thế điều này:

public static function camelize($word) {
   return preg_replace('/(^|_)([a-z])/e', 'strtoupper("\\2")', $word);
}

với preg_replace_callback với chức năng ẩn danh. Tôi không hiểu \\ 2 đang làm gì. Hoặc đối với vấn đề đó chính xác cách hoạt động của preg_replace_callback.

Mã chính xác để đạt được điều này là gì?


1
Công cụ sửa đổi e không được dùng nữa kể từ phiên bản PHP 5.5.0
HamZa

8
@HamZaDzCyberDeV Tôi biết. Đó là một trong những lý do tôi muốn thay thế nó bằng preg_replace_callback
Casey

2
Có một trang hướng dẫn sử dụng cho preg_replace_callback. Và \\2sẽ trở thành $matches[2]trong cuộc gọi lại đã nói. Hay bạn đang bối rối về phần nào cụ thể?
mario

@mario ahh $ trận đấu [2] là tất cả những gì tôi cần. Tôi vẫn không hiểu nó hoạt động như thế nào, nhưng nó có. Nếu bạn đưa nó vào một câu trả lời, tôi sẽ đánh dấu nó là giải quyết vấn đề.
Casey

3
Vui lòng không sử dụng create_function, nó chỉ là một trình bao bọc khác xung quanh eval. Bạn nên sử dụng một hàm ẩn danh thích hợp, trừ khi bạn bị mắc kẹt trong PHP 5.2 vì lý do nào đó.
IMSoP

Câu trả lời:


75

Trong một biểu thức chính quy, bạn có thể "nắm bắt" các phần của chuỗi được so khớp với (brackets); trong trường hợp này, bạn đang nắm bắt các phần (^|_)([a-z])của trận đấu. Chúng được đánh số bắt đầu từ 1, vì vậy bạn có tham chiếu ngược 1 và 2. Kết hợp 0 ​​là toàn bộ chuỗi được so khớp.

Công cụ /esửa đổi nhận một chuỗi thay thế và thay thế dấu gạch chéo ngược theo sau bằng một số (ví dụ \1) với tham chiếu ngược thích hợp - nhưng vì bạn đang ở trong một chuỗi, bạn cần thoát khỏi dấu gạch chéo ngược, vì vậy bạn nhận được '\\1'. Sau đó (hiệu quả) nó chạy evalđể chạy chuỗi kết quả như thể nó là mã PHP (đó là lý do tại sao nó không được dùng nữa, vì nó dễ sử dụng evaltheo cách không an toàn).

Các preg_replace_callbackchức năng thay vì phải mất một chức năng gọi lại và vượt qua nó một mảng chứa các hợp back-tài liệu tham khảo. Vì vậy, nơi bạn sẽ viết '\\1', thay vào đó bạn truy cập phần tử 1 của tham số đó - ví dụ: nếu bạn có một hàm ẩn danh của biểu mẫu function($matches) { ... }, tham chiếu ngược đầu tiên nằm $matches[1]bên trong hàm đó.

Vì vậy, một /elập luận của

'do_stuff(\\1) . "and" . do_stuff(\\2)'

có thể trở thành một cuộc gọi lại của

function($m) { return do_stuff($m[1]) . "and" . do_stuff($m[2]); }

Hoặc trong trường hợp của bạn

'strtoupper("\\2")'

có thể trở thành

function($m) { return strtoupper($m[2]); }

Lưu ý rằng $m$matcheskhông phải là tên ma thuật, chúng chỉ là tên tham số mà tôi đã đưa ra khi khai báo các hàm gọi lại của mình. Ngoài ra, bạn không cần phải chuyển một hàm ẩn danh, nó có thể là một tên hàm dưới dạng chuỗi hoặc một cái gì đó có dạng array($object, $method), như với bất kỳ lệnh gọi lại nào trong PHP , ví dụ:

function stuffy_callback($things) {
    return do_stuff($things[1]) . "and" . do_stuff($things[2]);
}
$foo = preg_replace_callback('/([a-z]+) and ([a-z]+)/', 'stuffy_callback', 'fish and chips');

Như với bất kỳ hàm nào, bạn không thể truy cập các biến bên ngoài lệnh gọi lại của mình (từ phạm vi xung quanh) theo mặc định. Khi sử dụng một hàm ẩn danh, bạn có thể sử dụng usetừ khóa để nhập các biến mà bạn cần truy cập, như đã thảo luận trong sổ tay PHP . ví dụ: nếu đối số cũ là

'do_stuff(\\1, $foo)'

thì lệnh gọi lại mới có thể trông giống như

function($m) use ($foo) { return do_stuff($m[1], $foo); }

Gotchas

  • Sử dụng preg_replace_callbackthay vì những /esửa đổi trên regex, vì vậy bạn cần phải loại bỏ cờ mà từ "mẫu" lập luận của bạn. Vì vậy, một mô hình như thế /blah(.*)blah/meisẽ trở thành /blah(.*)blah/mi.
  • Công cụ /esửa đổi đã sử dụng một biến thể của addslashes()nội bộ trên các đối số, vì vậy một số thay thế được sử dụng stripslashes()để loại bỏ nó; trong hầu hết các trường hợp, bạn có thể muốn xóa cuộc gọi đến stripslasheskhỏi cuộc gọi lại mới của mình.

1

Preg_replace shim có hỗ trợ eval

Điều này là rất khó lường. Nhưng nếu bạn không phải là một lập trình viên, hoặc thực sự thích mã khủng khiếp, bạn có thể sử dụng một preg_replacehàm thay thế để giữ cho /ecờ của bạn hoạt động tạm thời .

/**
 * Can be used as a stopgap shim for preg_replace() calls with /e flag.
 * Is likely to fail for more complex string munging expressions. And
 * very obviously won't help with local-scope variable expressions.
 *
 * @license: CC-BY-*.*-comment-must-be-retained
 * @security: Provides `eval` support for replacement patterns. Which
 *   poses troubles for user-supplied input when paired with overly
 *   generic placeholders. This variant is only slightly stricter than
 *   the C implementation, but still susceptible to varexpression, quote
 *   breakouts and mundane exploits from unquoted capture placeholders.
 * @url: https://stackoverflow.com/q/15454220
 */
function preg_replace_eval($pattern, $replacement, $subject, $limit=-1) {
    # strip /e flag
    $pattern = preg_replace('/(\W[a-df-z]*)e([a-df-z]*)$/i', '$1$2', $pattern);
    # warn about most blatant misuses at least
    if (preg_match('/\(\.[+*]/', $pattern)) {
        trigger_error("preg_replace_eval(): regex contains (.*) or (.+) placeholders, which easily causes security issues for unconstrained/user input in the replacement expression. Transform your code to use preg_replace_callback() with a sane replacement callback!");
    }
    # run preg_replace with eval-callback
    return preg_replace_callback(
        $pattern,
        function ($matches) use ($replacement) {
            # substitute $1/$2/… with literals from $matches[]
            $repl = preg_replace_callback(
                '/(?<!\\\\)(?:[$]|\\\\)(\d+)/',
                function ($m) use ($matches) {
                    if (!isset($matches[$m[1]])) { trigger_error("No capture group for '$m[0]' eval placeholder"); }
                    return addcslashes($matches[$m[1]], '\"\'\`\$\\\0'); # additionally escapes '$' and backticks
                },
                $replacement
            );
            # run the replacement expression
            return eval("return $repl;");
        },
        $subject,
        $limit
    );
}

Về bản chất, bạn chỉ cần đưa chức năng đó vào cơ sở mã của mình và chỉnh sửa preg_replace thành preg_replace_evalbất /ekỳ nơi nào cờ được sử dụng.

Ưu và nhược điểm :

  • Thực sự chỉ được thử nghiệm với một vài mẫu từ Stack Overflow.
  • Chỉ hỗ trợ các trường hợp dễ (gọi hàm, không phải tra cứu biến).
  • Chứa thêm một số hạn chế và thông báo tư vấn.
  • Sẽ gây ra các lỗi sai lệch và khó hiểu hơn đối với các lỗi diễn đạt.
  • Tuy nhiên, vẫn là một giải pháp tạm thời có thể sử dụng và không làm phức tạp quá trình chuyển đổi thích hợp sang preg_replace_callback.
  • Và nhận xét về giấy phép chỉ nhằm mục đích ngăn chặn mọi người lạm dụng hoặc lan truyền điều này quá xa.

Trình tạo mã thay thế

Bây giờ điều này là hơi thừa. Nhưng có thể giúp những người dùng vẫn còn quá tải với việc cấu trúc lại mã của họ theo cách thủ công preg_replace_callback. Mặc dù điều này thực sự tốn nhiều thời gian hơn, nhưng trình tạo mã ít gặp khó khăn hơn khi mở rộng /echuỗi thay thế thành một biểu thức. Đó là một chuyển đổi không đáng kể, nhưng có thể đủ cho các ví dụ phổ biến nhất.

Để sử dụng chức năng này, hãy chỉnh sửa bất kỳ preg_replacecuộc gọi bị hỏng nào vào preg_replace_eval_replacementvà chạy nó một lần . Điều này sẽ in ra theo preg_replace_callbackkhối sẽ được sử dụng ở vị trí của nó.

/**
 * Use once to generate a crude preg_replace_callback() substitution. Might often
 * require additional changes in the `return …;` expression. You'll also have to
 * refit the variable names for input/output obviously.
 *
 * >>>  preg_replace_eval_replacement("/\w+/", 'strtopupper("$1")', $ignored);
 */
function preg_replace_eval_replacement($pattern, $replacement, $subjectvar="IGNORED") {
    $pattern = preg_replace('/(\W[a-df-z]*)e([a-df-z]*)$/i', '$1$2', $pattern);
    $replacement = preg_replace_callback('/[\'\"]?(?<!\\\\)(?:[$]|\\\\)(\d+)[\'\"]?/', function ($m) { return "\$m[{$m[1]}]"; }, $replacement);
    $ve = "var_export";
    $bt = debug_backtrace(0, 1)[0];
    print "<pre><code>
    #----------------------------------------------------
    # replace preg_*() call in '$bt[file]' line $bt[line] with:
    #----------------------------------------------------
    \$OUTPUT_VAR = preg_replace_callback(
        {$ve($pattern, TRUE)},
        function (\$m) {
            return {$replacement};
        },
        \$YOUR_INPUT_VARIABLE_GOES_HERE
    )
    #----------------------------------------------------
    </code></pre>\n";
}

Hãy nhớ rằng chỉ sao chép và dán không phải là lập trình. Bạn sẽ phải điều chỉnh mã đã tạo trở lại với tên biến đầu vào / đầu ra thực tế của mình hoặc ngữ cảnh sử dụng.

  • Cụ thể, $OUTPUT =nhiệm vụ sẽ phải thực hiện nếu preg_replacecuộc gọi trước đó được sử dụng trong if.
  • Tốt nhất nên giữ các biến tạm thời hoặc cấu trúc khối mã đa dòng.

Và biểu thức thay thế có thể yêu cầu cải tiến hoặc làm lại khả năng đọc nhiều hơn.

  • Ví dụ, stripslashes()thường trở nên thừa trong các biểu thức nghĩa đen.
  • Tra cứu phạm vi biến yêu cầu một usehoặc globaltham chiếu cho / trong lệnh gọi lại.
  • Các "-$1-$2"tham chiếu chụp kèm theo dấu ngoặc kép không đồng đều sẽ kết thúc bằng cách chuyển đổi thành "-$m[1]-$m[2].

Đầu ra mã chỉ là một điểm bắt đầu. Và có, điều này sẽ hữu ích hơn như một công cụ trực tuyến. Cách tiếp cận viết lại mã này (chỉnh sửa, chạy, chỉnh sửa, chỉnh sửa) hơi không thực tế. Tuy nhiên, có thể dễ tiếp cận hơn với những người đã quen với việc mã hóa tập trung vào nhiệm vụ (nhiều bước hơn, nhiều khám phá hơn). Vì vậy, thay thế này có thể hạn chế một số câu hỏi trùng lặp hơn.


0

Bạn không nên sử dụng cờ e(hoặc evalnói chung).

Bạn cũng có thể sử dụng thư viện T-Regx

pattern('(^|_)([a-z])')->replace($word)->by()->group(2)->callback('strtoupper');
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.