Tags:
The preg_replace_callback()
function in PHP is one of the many regular expression functions available in the language, and can be incredibly powerful as they offer far more versatility when replacing matched text. I recently wanted to use it to implement a very basic templating class.
This function goes beyond the preg_replace()
function which is just a standard regular expression find/replace. The callback aspect is that a custom function or method can be run on each and every match. This allows you to replace the matched strings with more complex generated content.
Basic Usage
There are a variety of ways in which it can be used, not all of which are fully documented on the manual. Here are the ways in which to use it to its fullest:
Closures
This is the method given on the manual, and serves well for simple usages:
$text = "The time now is ~now~";
echo preg_replace_callback(
"/~now~/",
function($matches){
return date("H:i:s)
});
The anonymous function approach is simple, and easy to implement, but for complex code it starts to break the SOLID code principles, as complicated code isn't code which does just one thing.
Public, Private, and Static Methods
A more object orientated way of using this is to pass in a reference to a class method. The simplest form is the static method call, which behaves largelyl like a standard function in the global space:
class TimeHelper
{
public static function showTime($matches)
{
return date("H:i:s");
}
}
$text = "The time now is ~now~";
echo preg_replace_callback("/~now~/", "TimeHelper::showTime", $text);
Static methods work fine for very simple callback methods like this, but if you need to rely on the results of other classes being injected, or something more detailed, then you probably want to move towards private and public methods instead. Both of these take the following form:
class TimeHelper
{
public function showTime($matches)
{
return date("H:i:s");
}
}
$text = "The time now is ~now~";
$helper = new TimeHelper();
echo preg_replace_callback(
"/~now~/",
[$helper, "showTime"],
$text
);
Making a callback that points to a private method is basically the same, but can only be done within the class itself (this should be obvious), and you change the reference to the class (first argument in callback array) to $this
.
Passing Extra Parameters
The closure that PHP uses only accepts a single parameter, the array that's generated from the regular expression match. If you need to pass extra information through to the callback, there are two ways:
- The
use
statement on an inline closure - Helper class with data population calls
The use
Statement Approach
This is a simple example:
$data = [
"animal" => "cat",
"action" => "sat on",
"object" => "mat"
];
$text = "the ~animal~ ~action~ the ~object~";
echo preg_replace_callback(
"/~([^~]+)~/",
function($matches) use ($data) {
$matchedItem = $matches[1];
if(isset($data[$matchedItem]))
return $data[$matchedItem];
return '';
},
$text
);
The regular expression here just matches any content between a pair of tilde symbols, and the closure looks for an array element using that as a key to return as the replacement.
The Helper Class Approach
class StoryHelper
{
private $animal;
private $action;
private $object;
public function __construct($animal, $action, $object)
{
$this->animal = $animal;
$this->action = $action;
$this->object = $object;
}
public function tellStory($matches)
{
$matchedItem = $matches[1];
if(isset($this->{$matchedItem}))
return $this->{$matchedItem};
return '';
}
}
$text = "the ~animal~ ~action~ the ~object~";
$storyHelper = new StoryHelper("cow", "jumped over", "moon");
echo preg_replace_callback(
"/~([^~]+)~/",
[$storyHelper, "tellStory"],
$text
);
As you can see, this method is a little cleaner, and this method will make testing far easier. I'd absolutely recommend using a helper class like this for anything more complex than a simple variable substitution like we're doing here.
Conclusion
preg_replace_callback
is a very powerful function, and useful function. The concept exists in other languages too. Javascript allows callback functions in place of replacement strings and C# has it with its MatchEvaluator.
But as ever when using regular expressions, it's important to remember the words of Jamie Zawinski:
Some people, when confronted with a problem, think “I know, I'll use regular expressions.” Now they have two problems
Comments