I use preg_replace and I want to include a url inside the replacement string. How do I quote that string? It appears preg_quote is only for the search pattern.
$replace = '\1'.addslashes($url).'\3'.addslashes($title).'\4';
addslashes is not sufficientpreg_quote escapes too muchSee this demo.
As Mario commented you can use addcslashes($str, "\\$").
Unfortunately there is no generic way to do this, but addslashes should be enough in most cases.
For maximum safety you can use the ${1} syntax. E.g.
$replace = '${1}'.addslashes($url).'${3}'.addslashes($title).'${4}';
If you really want to be totally bulletproof, use a callback replacement function with preg_replace_callback(). The string returned from the callback function is used entirely as-is, so you don't have to worry about mixing replacement syntax with normal text.
Example with preg_replace_callback():
class URLReplacer {
public $pattern = '/my regex/';
public $url;
public $title;
function __construct($url, $title) {
$this->url = $url;
$this->title = $title;
}
function _callback($matches) {
return $matches[1].$url.$matches[3].$title.$matches[4];
}
function replace($subject) {
return preg_replace_callback($this->pattern, array($this, '_callback'), $subject);
}
}
$repl = new URLReplacer($url, $title);
$replaced = $repl->replace($subject);
preg_replace_callback() like array($callbackobj, 'callbackmethodname')/e expression as replacement string to pull them from the local scope.preg_replace("/(..)(..)/e", '"$1".$url."$2"', $src) -- Okay, now that I look at it; it's actually awful. But would be a shortcut to the callback approach.You did not provide an example, so I compiled one on my own. The working solution I came up with is using a simple callback function:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/';
$replace = function($m) use ($url, $title)
{
return "$m[1]$url$m[3]$title$m[4]";
};
$result = preg_replace_callback($pattern, $replace, $subject);
Result:
Call \4 " $me http://example.com/ Make it Complex \4url
The callback function is a so called anonymous function Docs which makes it easy to edit the code in place.
In case you need this more often you can put that into a function of your own, probably to make it more re-useable. You can even go to that far and create yourself your own pattern to replace subgroup matches and variables. For examle {\1} stands for the subpattern 1 match, {$2} for the second variable. Wrapping this into a function of it's own:
$patternf = function()
{
$values = func_get_args();
$mask = $values ? array_shift($values) : NULL;
return function($matches) use ($mask, $values)
{
$parts = preg_split('/({[\\\\\\$][0-9]{1,3}})/', $mask, 0, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach($parts as &$part)
if (preg_match('/^{([\\\\\\$])([0-9]{1,3})}$/', $part, $p))
$part = $p[1] == '\\' ? $matches[(int)$p[2]] : $values[$p[2]-1];
return implode('', $parts);
};
};
Would allow you to make the replacement more handy:
$replace = $patternf('{\1}{$1}{\3}{$2}{\4}', $url, $title);
$result = preg_replace_callback($pattern, $replace, $subject);
Demo. Wrapping this into a function of it's own:
function preg_replace_subst($pattern, $replace, $subject)
{
$values = func_get_args();
$pattern = array_shift($values);
$mask = array_shift($values);
$subject = array_shift($values);
$callback = function($matches) use ($mask, $values)
{
$parts = preg_split('/({[\\\\\\$][0-9]{1,3}})/', $mask, 0, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach($parts as &$part)
if (preg_match('/^{([\\\\\\$])([0-9]{1,3})}$/', $part, $p))
$part = $p[1] == '\\' ? $matches[(int)$p[2]] : $values[$p[2]-1];
return implode('', $parts);
};
return preg_replace_callback($pattern, $callback, $subject);
}
Would give it an easy interface:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/';
$replace = '{\1}{$1}{\3}{$2}{\4}';
$result = preg_replace_subst($pattern, $replace, $subject, $url, $title);
But with many substitution variables it should be possible to pass them as an array probably otherwise it gets a bit lengthy.
e modifier with preg_replace (and why it does not work)When using the e modifier, the matches get replaced in the replace string and it then get's evaluated. As other variables do not get escaped, the matches do interfere with PHP variable substitution, which is dangerous:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call me an url.';
$pattern = '/(.*)an()( )(url)/e';
$replace = '"$1{$url}$3{$title}$4"';
$result = preg_replace($pattern, $replace, $subject);
Outputs:
Call me http://example.com/ Make it Complex \4url.
As written, the first e-modifier example is broken because $ won't get escape in $subject, so PHP would have looked for unset variables. That's dangerous, too. I came up with a variant, it solves that issue but it can't handle double-quotes in the subject:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/e';
$replace = "'\$1'.\$url.'\$3'.\$title.'$4'";
Output:
Call \4 \" $me http://example.com/ Make it Complex \4url
^ problem, not in input.
So not really fool-proof, that's why it needs the callback function because it gets the matching sub-patterns unquoted.
preg_replace_callback with method callback for more complex replacements or code that needs to be maintained for a while.$subject contains $. I'm fiddling a bit, what I wrote about what get's escape by regex does not include $.addcslashes) works perfectly? What am I missing?To make it obvious one is escaping any potential back-references in the $replacement parameter of preg_replace(), use a function:
function preg_quote_replacement($input) {
return addcslashes($input, '\\$');
}
In OP's case:
$subject = preg_replace(
$pattern,
'\1'.preg_quote_replacement($url).'\3'.preg_quote_replacement($title).'\4',
$subject
);
You should use Prepared Patterns. It works like Prepared Statements in SQL:
Pattern::inject("\1@url\2@url\3", ['url' => $input]);
$1,$2, etc.preg_replaceso that the replacement is exactly what is originally in the string (no matter its content). What more would you want to know?$subject? What is$url? What is$title?. Just the basics, it's not clear from the question and would be good to have to give examples according to the question. To properly escape both the matches from$subjectas well as the variable values, I added an answer that deals with the deficiencies of any escape function that is applied prior replacement (namely the problem of double-escaping).