18

str_replace replaces all occurrences of a word with a replacement.

preg_replace replaces occurrences of a pattern with a replacement and takes an optional limit param.

I don't have any need for pattern matching but would like the convenience of a limit param. What should I be using?

buley
  • 28,032
  • 17
  • 85
  • 106

5 Answers5

18

Theres a better way to do this

<?
$str = 'abcdef abcdef abcdef';
// pattern, replacement, string, limit
echo preg_replace('/abc/', '123', $str, 1); // outputs '123def abcdef abcdef'
?>
rakibtg
  • 5,521
  • 11
  • 50
  • 73
5
function str_replace2($find, $replacement, $subject, $limit = 0){
  if ($limit == 0)
    return str_replace($find, $replacement, $subject);
  $ptn = '/' . preg_quote($find,'/') . '/';
  return preg_replace($ptn, $replacement, $subject, $limit);
}

That will allow you to place a limit on the number of replaces. Strings should be escaped using preg_quote to make sure any special characters aren't interpreted as a pattern character.

Demo, BTW


In case you're interested, here's a version that includes the &$count argument:

function str_replace2($find, $replacement, $subject, $limit = 0, &$count = 0){
  if ($limit == 0)
    return str_replace($find, $replacement, $subject, $count);
  $ptn = '/' . preg_quote($find,'/') . '/';
  return preg_replace($ptn, $replacement, $subject, $limit, $count);
}
Paul
  • 13
  • 3
Brad Christie
  • 100,477
  • 16
  • 156
  • 200
  • I'll take this as correct since it works to do what I'm looking for. This is near exactly the solution that I had settled on, but I wanted to avoid preg_replace overhead if possible given I need no regex/pattern matching. – buley Dec 14 '11 at 19:23
  • @editor: Well, you can write one that `strpos` and `substr`s for matches, but it'd be a bit more work (and arguably faster/flower since you're doing processing outside of the engine itself). It's up to you, though it would be interesting seeing a benchmark comparison. – Brad Christie Dec 14 '11 at 19:31
  • `$count` should come before `$limit`, to be backward-compatible with `str_replace()`. Also, your code only supports strings, whereas `str_replace()` supports arrays for `$search`, `$replace`, and `$subject`. – 0b10011 Jul 09 '12 at 17:51
  • 1
    @bfrohs: You're entitled to your opinion, however the solution i provided meets the needs of the poster. Furthermore, there was no requirement for backwards compatibility (nor should there be as, although similar, both differ in implementation) Above all, and I'm sorry if I incorrectly assumed, down-voting mine because you made a complete implementation (to your specs) 7 months later is just uncalled for. – Brad Christie Jul 09 '12 at 19:02
  • It has nothing to do with an answer being *my* answer. I feel your solution is misleading as the function is named `str_replace2` (which one would assume could be used instead of `str_replace`; `0` is used for "no limit", rather than the standard `-1`; you use your own variable names, which don't match up with `str_replace`; and your variable names are inconsistent (abbreviated "pattern" `$ptn` whereas the rest of the variables use full words). Yes, it's my opinion, but it would also be my opinion had I voted your answer up. If you fix some of these issues, I'd be happy to vote up :) – 0b10011 Jul 09 '12 at 19:32
5
$str = implode($replace, explode($search, $subject, $count + 1));

Quick PoC:

$str =
"To be, or not to be, that is the question:
Whether 'tis Nobler in the mind to suffer
The Slings and Arrows of outrageous Fortune,
Or to take Arms against a Sea of troubles,
And by opposing end them";

/* Replace the first 2 occurrences of 'to' with 'CAN IT' in $str. */
echo implode('CAN IT', explode('to', $str, 3));

Output (emphasis added):

To be, or not CAN IT be, that is the question:
Whether 'tis Nobler in the mind CAN IT suffer
The Slings and Arrows of outrageous Fortune,
Or to take Arms against a Sea of troubles,
And by opposing end them

Note that this method is case sensitive.

1

Originally pulled from https://stackoverflow.com/a/11400172/526741

I've written a function that is 100% backward-compatible with str_replace(). That is, you can replace all occurrences of str_replace() with str_replace_limit() without messing anything up, even those using arrays for the $search, $replace, and/or $subject.

The function could be completely self-contained, if you wanted to replace the function call with ($string===strval(intval(strval($string)))), but I'd recommend against it since valid_integer() is a rather useful function when dealing with integers provided as strings.

Note: Whenever possible, str_replace_limit() will use str_replace() instead, so all calls to str_replace() can be replaced with str_replace_limit() without worrying about a hit to performance.

Usage

<?php
$search = 'a';
$replace = 'b';
$subject = 'abcabc';
$limit = -1; // No limit
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2 replacements -- bbcbbc

$limit = 1; // Limit of 1
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

1 replacements -- bbcabc

$limit = 10; // Limit of 10
$new_string = str_replace_limit($search, $replace, $subject, $count, $limit);
echo $count.' replacements -- '.$new_string;

2 replacements -- bbcbbc

Function

<?php

/**
 * Checks if $string is a valid integer. Integers provided as strings (e.g. '2' vs 2)
 * are also supported.
 * @param mixed $string
 * @return bool Returns boolean TRUE if string is a valid integer, or FALSE if it is not 
 */
function valid_integer($string){
    // 1. Cast as string (in case integer is provided)
    // 1. Convert the string to an integer and back to a string
    // 2. Check if identical (note: 'identical', NOT just 'equal')
    // Note: TRUE, FALSE, and NULL $string values all return FALSE
    $string = strval($string);
    return ($string===strval(intval($string)));
}

/**
 * Replace $limit occurences of the search string with the replacement string
 * @param mixed $search The value being searched for, otherwise known as the needle. An
 * array may be used to designate multiple needles.
 * @param mixed $replace The replacement value that replaces found search values. An
 * array may be used to designate multiple replacements.
 * @param mixed $subject The string or array being searched and replaced on, otherwise
 * known as the haystack. If subject is an array, then the search and replace is
 * performed with every entry of subject, and the return value is an array as well. 
 * @param string $count If passed, this will be set to the number of replacements
 * performed.
 * @param int $limit The maximum possible replacements for each pattern in each subject
 * string. Defaults to -1 (no limit).
 * @return string This function returns a string with the replaced values.
 */
function str_replace_limit(
        $search,
        $replace,
        $subject,
        &$count,
        $limit = -1
    ){

    // Set some defaults
    $count = 0;

    // Invalid $limit provided. Throw a warning.
    if(!valid_integer($limit)){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting an '.
                'integer', E_USER_WARNING);
        return $subject;
    }

    // Invalid $limit provided. Throw a warning.
    if($limit<-1){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
                'a positive integer', E_USER_WARNING);
        return $subject;
    }

    // No replacements necessary. Throw a notice as this was most likely not the intended
    // use. And, if it was (e.g. part of a loop, setting $limit dynamically), it can be
    // worked around by simply checking to see if $limit===0, and if it does, skip the
    // function call (and set $count to 0, if applicable).
    if($limit===0){
        $backtrace = debug_backtrace();
        trigger_error('Invalid $limit `'.$limit.'` provided to '.__function__.'() in '.
                '`'.$backtrace[0]['file'].'` on line '.$backtrace[0]['line'].'. Expecting -1 or '.
                'a positive integer', E_USER_NOTICE);
        return $subject;
    }

    // Use str_replace() whenever possible (for performance reasons)
    if($limit===-1){
        return str_replace($search, $replace, $subject, $count);
    }

    if(is_array($subject)){

        // Loop through $subject values and call this function for each one.
        foreach($subject as $key => $this_subject){

            // Skip values that are arrays (to match str_replace()).
            if(!is_array($this_subject)){

                // Call this function again for
                $this_function = __FUNCTION__;
                $subject[$key] = $this_function(
                        $search,
                        $replace,
                        $this_subject,
                        $this_count,
                        $limit
                );

                // Adjust $count
                $count += $this_count;

                // Adjust $limit, if not -1
                if($limit!=-1){
                    $limit -= $this_count;
                }

                // Reached $limit, return $subject
                if($limit===0){
                    return $subject;
                }

            }

        }

        return $subject;

    } elseif(is_array($search)){
        // Only treat $replace as an array if $search is also an array (to match str_replace())

        // Clear keys of $search (to match str_replace()).
        $search = array_values($search);

        // Clear keys of $replace, if applicable (to match str_replace()).
        if(is_array($replace)){
            $replace = array_values($replace);
        }

        // Loop through $search array.
        foreach($search as $key => $this_search){

            // Don't support multi-dimensional arrays (to match str_replace()).
            $this_search = strval($this_search);

            // If $replace is an array, use the value of $replace[$key] as the replacement. If
            // $replace[$key] doesn't exist, just an empty string (to match str_replace()).
            if(is_array($replace)){
                if(array_key_exists($key, $replace)){
                    $this_replace = strval($replace[$key]);
                } else {
                    $this_replace = '';
                }
            } else {
                $this_replace = strval($replace);
            }

            // Call this function again for
            $this_function = __FUNCTION__;
            $subject = $this_function(
                    $this_search,
                    $this_replace,
                    $subject,
                    $this_count,
                    $limit
            );

            // Adjust $count
            $count += $this_count;

            // Adjust $limit, if not -1
            if($limit!=-1){
                $limit -= $this_count;
            }

            // Reached $limit, return $subject
            if($limit===0){
                return $subject;
            }

        }

        return $subject;

    } else {
        $search = strval($search);
        $replace = strval($replace);

        // Get position of first $search
        $pos = strpos($subject, $search);

        // Return $subject if $search cannot be found
        if($pos===false){
            return $subject;
        }

        // Get length of $search, to make proper replacement later on
        $search_len = strlen($search);

        // Loop until $search can no longer be found, or $limit is reached
        for($i=0;(($i<$limit)||($limit===-1));$i++){

            // Replace 
            $subject = substr_replace($subject, $replace, $pos, $search_len);

            // Increase $count
            $count++;

            // Get location of next $search
            $pos = strpos($subject, $search);

            // Break out of loop if $needle
            if($pos===false){
                break;
            }

        }

        // Return new $subject
        return $subject;

    }

}
Community
  • 1
  • 1
0b10011
  • 18,397
  • 4
  • 65
  • 86
1

This is an enhanced version of @brad-christie's answer which ads compatibility for arrays as $find and $replace values:

function str_replace_limit($find, $replacement, $subject, $limit = 0) {

    if ($limit == 0)
      return str_replace($find, $replacement, $subject);

    for ($i = 0; $i < count($find); $i++) {
      $find[$i] = '/' . preg_quote($find[$i],'/') . '/';
    }

    return preg_replace($find, $replacement, $subject, $limit);
  }
Parks
  • 620
  • 6
  • 8