Named backreferences with preg_replace - php

Pretty straightforward; I can't seem to find anything definitive regarding PHP's preg_replace() supporting named backreferences:
// should match, replace, and output: user/profile/foo
$string = 'user/foo';
echo preg_replace('#^user/(?P<id>[^/]+)$#Di', 'user/profile/(?P=id)', $string);
This is a trivial example, but I'm wondering if this syntax, (?P=name) is simply not supported. Syntactical issue, or non-existent functionality?

They exist:
http://www.php.net/manual/en/regexp.reference.back-references.php
With preg_replace_callback:
function my_replace($matches) {
return '/user/profile/' . $matches['id'];
}
$newandimproved = preg_replace_callback('#^user/(?P<id>[^/]+)$#Di', 'my_replace', $string);
Or even quicker
$newandimproved = preg_replace('#^user/([^/]+)$#Di', '/user/profile/$1', $string);

preg_replace does not support named backreferences.
preg_replace_callback supports named backreferences, but after PHP 5.3, so expect it to fail on PHP 5.2 and below.

preg_replace does not supported named subpatterns yet.

you can use this :
class oreg_replace_helper {
const REGEXP = '~
(?<!\x5C)(\x5C\x5C)*+
(?:
(?:
\x5C(?P<num>\d++)
)
|
(?:
\$\+?{(?P<name1>\w++)}
)
|
(?:
\x5Cg\<(?P<name2>\w++)\>
)
)?
~xs';
protected $replace;
protected $matches;
public function __construct($replace) {
$this->replace = $replace;
}
public function replace($matches) {
var_dump($matches);
$this->matches = $matches;
return preg_replace_callback(self::REGEXP, array($this, 'map'), $this->replace);
}
public function map($matches) {
foreach (array('num', 'name1', 'name2') as $name) {
if (isset($this->matches[$matches[$name]])) {
return stripslashes($matches[1]) . $this->matches[$matches[$name]];
}
}
return stripslashes($matches[1]);
}
}
function oreg_replace($pattern, $replace, $subject) {
return preg_replace_callback($pattern, array(new oreg_replace_helper($replace), 'replace'), $subject);
}
then you can use either \g<name> ${name} or $+{name} as reference in your replace statement.
cf (http://www.rexegg.com/regex-disambiguation.html#namedcapture)

Related

How to call a function in an preg_replace?

How can I call a function within preg_replace()?
$template = '<div> [BILD="123"][BILD="246"] </div>';
$pat = '/\[BILD="(.*?)"\]\[BILD ="(.*?)"\]/';
$ret = getImageArea($id, $config, '$1', '$2');
return preg_replace($pat, $rep, $template);
It fails opening the function and doesn't sends 123 or 246.
It just sends $1 and $2.
As I (and many others) told in comments, you have to use preg_replace_callback in this case. One possible approach:
$template = '<div> [BILD="123"][BILD="246"] </div>';
$pat = '/\[BILD="(.*?)"\]\[BILD="(.*?)"\]/';
return preg_replace_callback($pat, function($matches) use($id, $config) {
return getImageArea($id, $config, $matches[1], $matches[2]);
}, $template);
Demo. As you see, it's quite straight-forward; the only catch is making $id and $config variables available inside the callback (which is done with use op).

Symfony 1.4 using deprecated functions in php 5.5

I recently upgraded PHP from version 5.3.27 to 5.5.0.
Everything is working fine in my Symfony 2.3.2 project, and I can enjoy the latest PHP functionalities.
Now when I am going back to my other Symfony 1.4.16 project, I get a PHP error about preg_replace being deprecated with the /e modifier.
I can find no reference about this error in the forums: Has anyone had this problem before ? Is there any kind of patch that I could apply out of the box ? Is an upgrade to Symfony 1.4.20 going to fix this issue ?
The error message goes like this:
Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in /myproject/lib/vendor/symfony/lib/response/sfWebResponse.class.php on line 409
One way to go may be to modify the code as recommended in the message, and in the manual.
How can I change my preg_replace expression to a preg_replace_callback call ?
Any help / hint will be very welcome.
EDIT:
To this date, there is no patch for this (and Symfony 1.4.20 does not address the issue). The solution is to replace failing calls to preg_replace with corresponding call to preg_replace_callback in the sourche, which is easily done in the sfWebResponse class (thanks for the hint Jon). Now next failing occurrence is slightly more complex, unfortunately... And on the other hand, we probably would have to grep for preg_replace uses with /e option in order to find out where Symfony is likely to break. Which gives quite a few results :o
So... My conclusion would be that Symfony 1.4 users would better not upgrade PHP to version 5.5 until some serious patch comes out. What do you think ? Any alternative ?
The errors do not show up in prod unless you have enabled debug in index.php. It' s also possible to remove them in dev by unsetting the E_DEPRECATED flag in settings.yml :
dev:
.settings:
error_reporting: <?php echo ((E_ALL | E_STRICT) ^ E_DEPRECATED)."\n" ?>
Basically what you have to do is take the replacement argument from the preg_replace call and factor it out into a proper PHP expression, then make that expression the body of a function that will be used as the callback to the equivalent preg_replace_callback call.
In your case the relevant code is
return preg_replace('/\-(.)/e', "'-'.strtoupper('\\1')", /* input */)
So you would do that as
$callback = function($matches) {
return '-'.strtoupper($matches[1]);
};
return preg_replace_callback('/\-(.)/', $callback, /* input */)
As you can see the callback code is the same as the original replace expression, the only difference being that references such as \\1 are replaced with array accesses like $matches[1].
All in all, the best solution is to avoid upgrading PHP to version 5.5, as it is no more compatible with Symfony 1.4
If you have both Symfony 2 and 1.4 versions in a development environment, you may want to be able to switch your PHP version, as nicely described here.
If you really need to, it is possible to setup two different versions of PHP running on the same Apache server at the same time: this will need some more configuration, the above link explains that too.
Alternative HOT FIX:
With a couple of updates in the Symfony code, I can get most of my webpages running in dev. Of course, it would be dangerous to apply this in production, as the "deprecated" error may turn up again at any time, arising from another Symfony library.
In myproject/lib/vendor/symfony/lib/response/sfWebResponse.class.php on line 409, I have now (commented code is original Symfony code):
protected function normalizeHeaderName($name)
{
// return preg_replace('/\-(.)/e', "'-'.strtoupper('\\1')", strtr(ucfirst(strtolower($name)), '_', '-'));
return preg_replace_callback(
'/\-(.)/',
function ($matches) {
return '-'.strtoupper($matches[1]);
},
strtr(ucfirst(strtolower($name)), '_', '-')
);
}
And in myproject/lib/vendor/symfony/lib/util/sfToolkit.class.php on line 362 we get:
public static function pregtr($search, $replacePairs)
{
// return preg_replace(array_keys($replacePairs), array_values($replacePairs), $search);
foreach($replacePairs as $pattern => $replacement)
$search = preg_replace_callback(
$pattern,
function ($matches) use ($replacement){
if(array_key_exists(1, $matches)){ $replacement = str_replace("\\1", $matches[1], $replacement);}
if(array_key_exists(2, $matches)){ $replacement = str_replace("\\2", $matches[2], $replacement);}
return $replacement;
},
$search
);
return $search;
}
Use at your own risks :)
FIX for normalizeHeaderName method in /lib/vendor/symfony/lib/response/sfWebResponse.class.php on line 407
protected function normalizeHeaderName($name)
{
//return preg_replace('/\-(.)/e', "'-'.strtoupper('\\1')",
strtr(ucfirst(strtolower($name)), '_', '-');
return str_replace(array('\'$1$3\'','\'$2$4\'','\'$1\'', '\'$2\'', '$1', '$2'),array('$matches[1].$matches[3]','$matches[2].$matches[4]','$matches[1]','$matches[2]','$matches[1]','$matches[2]'),
$name);
}
FIX for pregtr method in /lib/vendor/symfony/lib/util/sfToolkit.class.php on line 360
public static function pregtr($search, $replacePairs){
// return preg_replace(array_keys($replacePairs), array_values($replacePairs), $search);
foreach($replacePairs as $pattern => $replacement)
{
if (preg_match('/(.*)e$/', $pattern, $matches))
{
$pattern = $matches[1];
$search = preg_replace_callback($pattern, function ($matches) use ($replacement) {
preg_match("/('::'\.)?([a-z]*)\('\\\\([0-9]{1})'\)/", $replacement, $match);
return ($match[1]==''?'':'::').call_user_func($match[2], $matches[$match[3]]);
}, $search);
}
else
{
$search = preg_replace($pattern, $replacement, $search);
}
}
return $search;
}
There is a community version of Symfony that maintains and patches the older code:
https://github.com/LExpress/symfony1
Alternative FIX for pregtr method in /lib/vendor/symfony/lib/util/sfToolkit.class.php on line 360
public static function pregtr($search, $replacePairs)
{
// return preg_replace(array_keys($replacePairs), array_values($replacePairs), $search);
foreach($replacePairs as $pattern => $replacement)
{
if (preg_match('/(.*)e$/', $pattern, $matches))
{
$pattern = $matches[1];
$search = preg_replace_callback($pattern, function ($matches) use ($replacement) {
preg_match("/('::'\.)?([a-z]*)\('\\\\([0-9]{1})'\)/", $replacement, $match);
return ($match[1]==''?'':'::').call_user_func($match[2], $matches[$match[3]]);
}, $search);
}
else
{
$search = preg_replace($pattern, $replacement, $search);
}
}
return $search;
}
Deprecated: preg_replace(): The /e modifier is deprecated, use preg_replace_callback instead in lib/vendor/symfony/…This changelog will solve the problem for all symfony 1.4.x. Tested on Symfony 1.4.20
---
lib/vendor/symfony/lib/command/sfCommandManager.class.php | 4 +++-
lib/vendor/symfony/lib/form/addon/sfFormObject.class.php | 2 +-
lib/vendor/symfony/plugins/sfDoctrinePlugin/lib/form/sfFormFilterDoctrine.class.php | 2 +-
lib/vendor/symfony/plugins/sfPropelPlugin/lib/form/sfFormFilterPropel.class.php | 2 +-
lib/vendor/symfony/lib/response/sfWebResponse.class.php | 2 +-
lib/vendor/symfony/lib/util/sfInflector.class.php | 5 +----
lib/vendor/symfony/lib/util/sfToolkit.class.php | 11 +++++++++++
7 files changed, 19 insertions(+), 9 deletions(-)
lib/vendor/symfony/lib/command/sfCommandManager.class.php
## -108,7 +108,9 ## class sfCommandManager
else if (!is_array($arguments))
{
// hack to split arguments with spaces : --test="with some spaces"
- $arguments = preg_replace('/(\'|")(.+?)\\1/e', "str_replace(' ', '=PLACEHOLDER=', '\\2')", $arguments);
+ $arguments = preg_replace_callback('/(\'|")(.+?)\\1/', function($matches) {
+ return str_replace(' ', '=PLACEHOLDER=', $matches[2]);
+ }, $arguments);
$arguments = preg_split('/\s+/', $arguments);
$arguments = str_replace('=PLACEHOLDER=', ' ', $arguments);
}
lib/vendor/symfony/lib/form/addon/sfFormObject.class.php
## -278,6 +278,6 ## abstract class sfFormObject extends BaseForm
protected function camelize($text)
{
- return preg_replace(array('#/(.?)#e', '/(^|_|-)+(.)/e'), array("'::'.strtoupper('\\1')", "strtoupper('\\2')"), $text);
+ return sfToolkit::camelize($text);
}
}
lib/vendor/symfony/lib/plugins/sfDoctrinePlugin/lib/form/sfFormFilterDoctrine.class.php
## -323,7 +323,7 ## abstract class sfFormFilterDoctrine extends sfFormFilter
protected function camelize($text)
{
- return sfToolkit::pregtr($text, array('#/(.?)#e' => "'::'.strtoupper('\\1')", '/(^|_|-)+(.)/e' => "strtoupper('\\2')"));
+ return sfToolkit::camelize($text);
}
protected function getTable()
lib/vendor/symfony/lib/plugins/sfPropelPlugin/lib/form/sfFormFilterPropel.class.php
## -263,6 +263,6 ## abstract class sfFormFilterPropel extends sfFormFilter
protected function camelize($text)
{
- return sfToolkit::pregtr($text, array('#/(.?)#e' => "'::'.strtoupper('\\1')", '/(^|_|-)+(.)/e' => "strtoupper('\\2')"));
+ return sfToolkit::camelize($text);
}
}
lib/vendor/symfony/lib/response/sfWebResponse.class.php
## -406,7 +406,7 ## class sfWebResponse extends sfResponse
*/
protected function normalizeHeaderName($name)
{
- return preg_replace('/\-(.)/e', "'-'.strtoupper('\\1')", strtr(ucfirst(strtolower($name)), '_', '-'));
+ return preg_replace_callback('/\-(.)/', function ($matches) { return '-'.strtoupper($matches[1]); }, strtr(ucfirst(strtolower($name)), '_', '-'));
}
/**
lib/vendor/symfony/lib/util/sfInflector.class.php
## -28,10 +28,7 ## class sfInflector
public static function camelize($lower_case_and_underscored_word)
{
$tmp = $lower_case_and_underscored_word;
- $tmp = sfToolkit::pregtr($tmp, array('#/(.?)#e' => "'::'.strtoupper('\\1')",
- '/(^|_|-)+(.)/e' => "strtoupper('\\2')"));
-
- return $tmp;
+ return sfToolkit::camelize($tmp);;
}
/**
lib/vendor/symfony/lib/util/sfToolkit.class.php
## -608,4 +608,15 ## class sfToolkit
return set_include_path(join(PATH_SEPARATOR, $paths));
}
+
+ public static function camelize($text)
+ {
+ if (preg_match('#/(.?)#', $text, $matches)) {
+ $text = str_replace($matches[0], '::'.strtoupper($matches[1]), $text);
+ }
+ if (preg_match('/(^|_|-)+(.)/', $text, $matches)) {
+ $text = str_replace($matches[0], strtoupper($matches[2]), $text);
+ }
+ return $text;
+ }
}
--

Improving speed of regex and using preg_replace for slugs

Previously I have been echoing $obj->html but a current project requires that the HTML be examined for slugs like {whatever} and replacing these with other content.
I have two problems. The first is that this code is slower than I would like:
class Foo {
function draw_content() {
$slug = "/(?<=\{).*(?=\})/";
if (preg_match($slug, $this->html, $matches)) {
foreach ($matches as $match) {
if (method_exists($this,$match)) {
$replacement = $this->$match();
$this->html = preg_replace("/\{$match\}/", $replacement, $this->html);
}
}
}
return $this->html;
} // fn
function new_releases() {
echo "new release book covers";
} // fn
} // class
Is there a better way to get the slug contents? I presume the regex is what is slowing this down?
The second issue is stranger to me. Given this $obj->html:
<p class="headline">New Releases</p>
<p>xxx{new_releases}xxxx</p>
The processed output of $obj->draw_content() is drawn by <?=$obj->draw_content()?>
new release book covers<p class="headline">New Releases</p>
<p>xxxxxxx</p>
Why is the new_releases() output prepended? The slug is gone but the replacement is not in it's place!
you can replace your pattern by:
$slug = '~{\K[^}]*+(?=})~';
IMHO, you should replace your preg_match test and your preg_replace by an only preg_replace_callback function, try something like this (and correct the bugs :).
function draw_content() {
$slug = '~{([^}]*+)}~';
$that = $this;
$this->html = preg_replace_callback( $slug, function ($m) use ($that) {
if (method_exists($that, $m[1]))
return $that->$m[1]();
return $m[0]; }, $this->html);
return $this->html;
}

preg_replace() [function.preg-replace]: Delimiter must not be alphanumeric or backslash in

I want to replace every "../" and "script/uploaded" in my string variable to "" !
I have one function like this :
public function mypregReplace($v)
{
return preg_replace(
array("%script/uploaded%" , ""),
array( "[\.\./]" , ""),
$v);
}
but it showa me this error
preg_replace() [function.preg-replace]: Delimiter must not be
alphanumeric or backslash in
what`s wrong on my pattern?!
Your format is wrong, and you dont need preg_replace for this. Use str_replace
public function mypregReplace($v)
{
return str_replace(
array("script/uploaded" , "../"),
'',
$v);
}
You have your array wrong. The first array should consist only of patterns, and the second should be the replacement values. You want:
public function mypregReplace($v)
{
return preg_replace(
array("%script/uploaded%", "%\.\./%"),
array("", ""),
$v);
}
Which can be further simplified to:
public function mypregReplace($v)
{
return preg_replace(
array('%script/uploaded%', '%\.\./%'),
'',
$v);
}
You should be using str_replace for this, however. You don't need regular expressions to match exact strings:
public function mypregReplace($v)
{
return str_replace(array('script/uploaded', '../'), '', $v);
}

Replace in each paragraph

In $var there is some code. I'm trying to operate a nl2br() on text inside each <p></p>.
echo preg_replace('/<p>(.*?)</p>/i', nl2br('${1}'), $var);
This code doesn't work.
How do I fix this?
You probably need to escape <\/p>
It looks like preg_replace_callback might be what you're looking for: http://us3.php.net/manual/en/function.preg-replace-callback.php
ETA: In your specific example, you could either use an anonymous function (only if you're doing this once or twice, otherwise it eats up memory) or define a helper function
function nl2br_wrapper($matches)
{
return "<p>".nl2br($matches[1])."</p>";
}
preg_replace_callback('/<p>(.*?)<\/p>/si', "nl2br_wrapper", $var);
Like kernal mentions you could use an anonymous function(PHP 5)
function replaceText($data, $html)
{
$callback = function ($matches) use ($data){
return ( isset($data[$matches[1]]) )
? nl2br($data[$matches[1]])
: $matches[0];
};
return preg_replace_callback(
'/\<p>(.*?)\</p>',
$callback,
$html);
}
echo replaceText($replace_with, $html);

Categories