4

I have an output buffer with callback function. When cleaning the buffer the callback function is executed, however, the string returned isn't being altered.

I'm using following code:

<?php
ob_start('callback');
print 'some text';
error_log(ob_get_clean());

function callback($content) {
  error_log('callback');
  return $content . ' altered'; 
}

Output:

callback
some text

What I want:

callback
some text altered

What am I missing? I'm using PHP 5.3.10 in CLI.

Edit: the callback is being executed.

From the PHP manual:

The function will be called when the output buffer is flushed (sent) or cleaned (with ob_flush(), ob_clean() or similar function) or when the output buffer is flushed to the browser at the end of the request.

Bart
  • 433
  • 3
  • 19
  • Does the php manual say this includes ob_get_clean()? Your experiment is a strong argument to say that it doesn't. Also, it seems like maybe the buffer is being modified but the result of the get is not. –  Sep 12 '12 at 16:19

6 Answers6

9

I'm not sure if this is a bug or a feature. Looking at the PHP source code, I found that the return value of ob_get_clean is filled before the callback is invoked.

I see at least two workarounds. The first is to manually invoke the callback on the output string yourself. I think this needs no example.

The second is to exploit the possibility to stack output buffering. Since flushing successfully uses the callback, you can wrap the output code inside an additional output buffer and get the modified contents.

ob_start();

function callback($input) { return $input . " altered"; }
ob_start('callback');
echo "foo";
ob_end_flush();

$content = ob_get_clean();
ob_end_clean();
echo $content . "\n"; // prints "foo altered\n"

See the source code for ob_get_clean (main/output.c) if you're curious. You can get the source code at the PHP website. Here are some pointers.

/* {{{ proto bool ob_get_clean(void)
   Get current buffer contents and delete current output buffer */
PHP_FUNCTION(ob_get_clean)
{
    if (zend_parse_parameters_none() == FAILURE) {
        return;
    }

    // THIS CALL FILLS THE RETURN VALUE
    if (php_ob_get_buffer(return_value TSRMLS_CC) == FAILURE) {
        RETURN_FALSE;
    }

    if (!OG(ob_nesting_level)) {
        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer. No buffer to delete");
        zval_dtor(return_value);
        RETURN_FALSE;
    }
    if (OG(ob_nesting_level) && !OG(active_ob_buffer).status && !OG(active_ob_buffer).erase) {
        php_error_docref("ref.outcontrol" TSRMLS_CC, E_NOTICE, "failed to delete buffer %s", OG(active_ob_buffer).handler_name);
        zval_dtor(return_value);
        RETURN_FALSE;
    }

    // THIS CALL KILLS THE CURRENT BUFFER AND EXECUTES THE CALLBACK
    php_end_ob_buffer(0, 0 TSRMLS_CC);
}
/* }}} */

php_end_ob_buffer takes the contents of the OB buffer and applies the callback to it. If the first parameter is true, it passes the contents to the next output buffering handler. In this case it's false, so the content is lost even though it executed the callback.

zneak
  • 134,922
  • 42
  • 253
  • 328
1

If i had a guess it would be this.

ob_get_clean() is returning the result of the buffer, and THEN cleaning it, triggering the callback which modifies the content.

IE

'some text' is extracted from the buffer and prepared to be returned as per the requirement of the 'get' functionality.

Next, the buffer is cleaned, but before cleaning the callback is triggered against the content, as per the requirement for various ob functions when a callback is present.

The result is that the buffer is returned (as requested) but it is modified after, since the get happens before the clean.

-1

The CLI for PHP does not use output buffering (or more specifically the buffing is not related to the ob_ functions). So your callback is being skipped.

EDIT: Actually I'm having trouble confirming whether standard output buffering is available at the CLI. I would try ob_end_flush(), ob_flush(), and flush().

Matt S
  • 14,976
  • 6
  • 57
  • 76
-1

It will be called when buffer is flushed:

ob_start('callback');
print 'some text';
ob_end_flush();

function callback($content) {
  return $content . ' altered'; 
}

P.S. it works in CLI too.

ioseb
  • 16,625
  • 3
  • 33
  • 29
-1

I remove ob_get_clean and your code does work.

ob_start('callback');
print 'some text';
//error_log(ob_get_clean());

$buffer = ob_get_flush();

function callback($content) {
  return $content . ' altered';
}

I've check the output and it was some text alerted.

why you use ob_get_clean() method? it cleans the buffer.

hamedkh
  • 909
  • 3
  • 18
  • 35
-1
<?php ob_start('callback'); ?>

    Foo Bar Baz

<?php
  ob_end_flush();

  function callback($content) {
    $find = "Baz";
    $replace_with = "Foo";
    return (
      str_replace($find, $replace_with, $content)
    );
  }
?>
Judd Lyon
  • 538
  • 1
  • 4
  • 11