Using str_replace so that it only acts on the first match?

后端 未结 22 935
醉酒成梦
醉酒成梦 2020-11-22 11:03

I want a version of str_replace() that only replaces the first occurrence of $search in the $subject. Is there an easy solution to thi

相关标签:
22条回答
  • 2020-11-22 11:33

    The easiest way would be to use regular expression.

    The other way is to find the position of the string with strpos() and then an substr_replace()

    But i would really go for the RegExp.

    0 讨论(0)
  • 2020-11-22 11:33

    To expand on @renocor's answer, 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;
    
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 11:36

    Can be done with preg_replace:

    function str_replace_first($from, $to, $content)
    {
        $from = '/'.preg_quote($from, '/').'/';
    
        return preg_replace($from, $to, $content, 1);
    }
    
    echo str_replace_first('abc', '123', 'abcdef abcdef abcdef'); 
    // outputs '123def abcdef abcdef'
    

    The magic is in the optional fourth parameter [Limit]. From the documentation:

    [Limit] - The maximum possible replacements for each pattern in each subject string. Defaults to -1 (no limit).


    Though, see zombat's answer for a more efficient method (roughly, 3-4x faster).

    0 讨论(0)
  • 2020-11-22 11:36

    There's no version of it, but the solution isn't hacky at all.

    $pos = strpos($haystack, $needle);
    if ($pos !== false) {
        $newstring = substr_replace($haystack, $replace, $pos, strlen($needle));
    }
    

    Pretty easy, and saves the performance penalty of regular expressions.


    Bonus: If you want to replace last occurrence, just use strrpos in place of strpos.

    0 讨论(0)
  • 2020-11-22 11:38

    I created this little function that replaces string on string (case-sensitive) with limit, without the need of Regexp. It works fine.

    function str_replace_limit($search, $replace, $string, $limit = 1) {
        $pos = strpos($string, $search);
    
        if ($pos === false) {
            return $string;
        }
    
        $searchLen = strlen($search);
    
        for ($i = 0; $i < $limit; $i++) {
            $string = substr_replace($string, $replace, $pos, $searchLen);
    
            $pos = strpos($string, $search);
    
            if ($pos === false) {
                break;
            }
        }
    
        return $string;
    }
    

    Example usage:

    $search  = 'foo';
    $replace = 'bar';
    $string  = 'foo wizard makes foo brew for evil foo and jack';
    $limit   = 2;
    
    $replaced = str_replace_limit($search, $replace, $string, $limit);
    
    echo $replaced;
    // bar wizard makes bar brew for evil foo and jack
    
    0 讨论(0)
  • 2020-11-22 11:39

    This function is heavily inspired by the answer by @renocor. It makes the function multi byte safe.

    function str_replace_limit($search, $replace, $string, $limit)
    {
        $i = 0;
        $searchLength = mb_strlen($search);
    
        while(($pos = mb_strpos($string, $search)) !== false && $i < $limit)
        {
            $string = mb_substr_replace($string, $replace, $pos, $searchLength);
            $i += 1;
        }
    
        return $string;
    }
    
    function mb_substr_replace($string, $replacement, $start, $length = null, $encoding = null)
    {
        $string = (array)$string;
        $encoding = is_null($encoding) ? mb_internal_encoding() : $encoding;
        $length = is_null($length) ? mb_strlen($string) - $start : $length;
    
        $string = array_map(function($str) use ($replacement, $start, $length, $encoding){
    
            $begin = mb_substr($str, 0, $start, $encoding);
            $end = mb_substr($str, ($start + $length), mb_strlen($str), $encoding);
    
            return $begin . $replacement . $end;
    
        }, $string);
    
        return ( count($string) === 1 ) ? $string[0] : $string;
    }
    
    0 讨论(0)
提交回复
热议问题