Bumped into a problem today… PHProblem!
Let's say I have a string where I know a certain substring repeats 5 times. I want to replace occurance (0,2,3) with another string but leave occurence 1,4 alone. Or just replace the first 3 occurences and let the rest live. Of course this needs to be case sensitive/insensitive.
How do you do that in PHP with one simple function?
And the winning answer is… You Don't!
So I had to write my own function to do the following.
- Limit the replacements number. str_replace replaces all matches but I need to limit this to first X.
- Specify the replacements indexes. I want to replace match 0,2 and 4 and let the rest be.
- Indexes Array or Numeric Limit can be Negative. If I ask the function to replace -1 characters or index -1 I want last occurence gone.
- Indexes Can Work Combined. I want to replace match 0,1,-1 and let the rest be.
- I need to config case sensitivity as parameter. I want my function to be both case sensitive and insensitive friendly.
Is this too much to ask?
The approach:
I will show you the function declaration and explain each parameter.
function str_replace_limit($search, $replace, $subject, $limit = 0, $casensitive = false)
- $search is the string you search to replace.
- $replace is the replacement. Set to null to remove.
- $subject is the haystack where you replace the searched.
- $limit is the limit of replacements. It can be a number X meaning delete first X matches or it can be an array like array(0,2) replacing the first (0) and third (2) occurence and leaving the rest untouched. If numbers are negative occurences are calculated from the end.
- $casensitive is boolean. true = sensitive , false = insensitive.
Is there a more simple approach?
Not really. My function may look scary upfront but you don't need to understand it to use it. I have my own C++ way of handling things and some code there will shock the PHP coder but will look very natural to C++ ones. There were a few obstacles to overcome and if you really want to understand how this function works I'll try to explain it but if you don't you'll spare me a world full of trouble.
The function has a funny way of working. It splits the text and keeps null place holders where the searched string was found. Then, based on indexes, it replaces the place holders with the original string or the $replace string if marked for replace. Then the array is combined and output. I keep another array with the exact case of the matches so, when I put those that don't need to be replaced back, I have their original selves with uppers and lowers and so on :)
The PHP Code + Helper Functions.
<?
function str_replace_limit($search, $replace, $subject, $limit = 0, $casensitive = false){
if(!is_string($search) || !is_string($subject) || !strlen($search) || !strlen($subject))
return $subject;
if(
(!is_numeric($limit) && !is_array($limit)) ||
(is_array($limit) && (count($limit)==0))
) $limit = 0;
if(!$limit && $casensitive)
return str_replace($search, $replace, $subject);
if(!$limit && function_exists("str_ireplace"))
return str_ireplace($search, $replace, $subject);
$occurence_matrix = array();
$occurences_matrix = array();
while(true){
$str_location = ($casensitive ? strstr($subject, $search) : stristr($subject, $search));
if(!$str_location) break;
array_pop($occurence_matrix);
$slice_font_len = strlen($subject)-strlen($str_location);
$slice_front = null;
if($slice_font_len)
$slice_front = substr($subject, 0, $slice_font_len);
$slice_back = substr($str_location, strlen($search));
$subject = $slice_back;
array_push($occurence_matrix, (string)$slice_front);
array_push($occurence_matrix, null);
array_push($occurences_matrix, substr($str_location, 0, strlen($search)));
array_push($occurence_matrix, $slice_back);
}
if(!count($occurence_matrix)) return $subject;
$null_positions = array(); $pos = 0;
while($pos<count($occurence_matrix)){
if(is_null($occurence_matrix[$pos])) array_push($null_positions,$pos);
$pos++;
}
if(is_numeric($limit)){
$limit_len = $limit;
$limit = array_keys($null_positions);
if($limit_len>0){
$limit = array_slice($limit, 0, abs($limit_len));
}elseif($limit_len<0){
$limit = array_slice($limit, abs($limit_len));
}
}
$replace_pos = 0;
foreach($occurence_matrix as $key => $value){
if(!is_null($value)) continue;
$should_replace = (bool)in_array($replace_pos, $limit);
if(!$should_replace){
$should_replace = (bool)in_array($replace_pos-count($null_positions), $limit);
}
$occurence_matrix[$key] = ($should_replace ? $replace : $occurences_matrix[$replace_pos]);
$replace_pos++;
}
return implode(null,$occurence_matrix);
}
function str_nreplace($search, $replace, $subject, $limit = 0){
return str_replace_limit($search, $replace, $subject, $limit, true);
}
function stri_nreplace($search, $replace, $subject, $limit = 0){
return str_replace_limit($search, $replace, $subject, $limit, false);
}
function str_remove_limit($search, $subject, $limit = 0, $casensitive = false){
return str_replace_limit($search, null, $subject, $limit, $casensitive);
}
function str_nremove($search, $subject, $limit = 0){
return str_remove_limit($search, $subject, $limit, true);
}
function stri_nremove($search, $subject, $limit = 0){
return str_remove_limit($search, $subject, $limit, false);
}
?>
Have I tested it?
I have it used on several sites and works perfectly. If you want to know the count of matches use substr_count. If will hint you about the removable indexes. If you find bugs or have ideas you know the drill. Comment form is below :) Holler!