Skip to content

Deprecate returning non-string values from a user output handler #18932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jul 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,13 @@ PHP 8.5 UPGRADE NOTES
4. Deprecated Functionality
========================================

- Core:
. Returning a non-string from a user output handler is deprecated. The
deprecation warning will bypass the handler with the bad return to ensure
it is visible; if there are nested output handlers the next one will still
be used.
RFC: https://wiki.php.net/rfc/deprecations_php_8_4

- Hash:
. The MHASH_* constants have been deprecated. These have been overlooked
when the mhash*() function family has been deprecated per
Expand Down
1 change: 1 addition & 0 deletions Zend/tests/concat/bug79836.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ $counter = 0;
ob_start(function ($buffer) use (&$c, &$counter) {
$c = 0;
++$counter;
return '';
}, 1);
$c .= [];
$c .= [];
Expand Down
1 change: 1 addition & 0 deletions Zend/tests/concat/bug79836_1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ opcache.optimization_level = 0x7FFEBFFF & ~0x400
$x = 'non-empty';
ob_start(function () use (&$c) {
$c = 0;
return '';
}, 1);
$c = [];
$x = $c . $x;
Expand Down
1 change: 1 addition & 0 deletions Zend/tests/concat/bug79836_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ $c = str_repeat("abcd", 10);

ob_start(function () use (&$c) {
$c = 0;
return '';
}, 1);

class X {
Expand Down
1 change: 1 addition & 0 deletions Zend/tests/declare/gh18033_2.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ob_start(function() {
register_tick_function(
function() { }
);
return '';
});
?>
--EXPECT--
1 change: 1 addition & 0 deletions Zend/tests/gh11189.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ob_start(function() {
$a[] = 2;
}
fwrite(STDOUT, "Success");
return '';
});

$a = [];
Expand Down
1 change: 1 addition & 0 deletions Zend/tests/gh11189_1.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ ob_start(function() {
$a[] = 2;
}
fwrite(STDOUT, "Success");
return '';
});

$a = ["not packed" => 1];
Expand Down
1 change: 1 addition & 0 deletions Zend/tests/gh16408.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ $counter = 0;
ob_start(function ($buffer) use (&$c, &$counter) {
$c = 0;
++$counter;
return '';
}, 1);
$c .= [];
$c .= [];
Expand Down
2 changes: 1 addition & 1 deletion ext/session/tests/user_session_module/bug61728.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ session
--FILE--
<?php
function output_html($ext) {
return strlen($ext);
return (string)strlen($ext);
}

class MySessionHandler implements SessionHandlerInterface {
Expand Down
81 changes: 68 additions & 13 deletions main/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,7 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
return PHP_OUTPUT_HANDLER_FAILURE;
}

bool still_have_handler = true;
/* storable? */
if (php_output_handler_append(handler, &context->in) && !context->op) {
context->op = original_op;
Expand All @@ -948,6 +949,7 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
if (handler->flags & PHP_OUTPUT_HANDLER_USER) {
zval ob_args[2];
zval retval;
ZVAL_UNDEF(&retval);

/* ob_data */
ZVAL_STRINGL(&ob_args[0], handler->buffer.data, handler->buffer.used);
Expand All @@ -959,17 +961,48 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
handler->func.user->fci.params = ob_args;
handler->func.user->fci.retval = &retval;

#define PHP_OUTPUT_USER_SUCCESS(retval) ((Z_TYPE(retval) != IS_UNDEF) && !(Z_TYPE(retval) == IS_FALSE))
if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && PHP_OUTPUT_USER_SUCCESS(retval)) {
/* user handler may have returned TRUE */
status = PHP_OUTPUT_HANDLER_NO_DATA;
if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
convert_to_string(&retval);
if (Z_STRLEN(retval)) {
context->out.data = estrndup(Z_STRVAL(retval), Z_STRLEN(retval));
context->out.used = Z_STRLEN(retval);
context->out.free = 1;
status = PHP_OUTPUT_HANDLER_SUCCESS;
if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && Z_TYPE(retval) != IS_UNDEF) {
if (Z_TYPE(retval) != IS_STRING) {
// Make sure that we don't get lost in the current output buffer
// by disabling it
handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
php_error_docref(
NULL,
E_DEPRECATED,
"Returning a non-string result from user output handler %s is deprecated",
ZSTR_VAL(handler->name)
);
// Check if the handler is still in the list of handlers to
// determine if the PHP_OUTPUT_HANDLER_DISABLED flag can
// be removed
still_have_handler = false;
int handler_count = php_output_get_level();
if (handler_count) {
php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
for (int handler_num = 0; handler_num < handler_count; ++handler_num) {
php_output_handler *curr_handler = handlers[handler_num];
if (curr_handler == handler) {
handler->flags &= (~PHP_OUTPUT_HANDLER_DISABLED);
still_have_handler = true;
break;
}
}
}
}
if (Z_TYPE(retval) == IS_FALSE) {
/* call failed, pass internal buffer along */
status = PHP_OUTPUT_HANDLER_FAILURE;
} else {
/* user handler may have returned TRUE */
status = PHP_OUTPUT_HANDLER_NO_DATA;
if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) {
convert_to_string(&retval);
if (Z_STRLEN(retval)) {
context->out.data = estrndup(Z_STRVAL(retval), Z_STRLEN(retval));
context->out.used = Z_STRLEN(retval);
context->out.free = 1;
status = PHP_OUTPUT_HANDLER_SUCCESS;
}
}
}
} else {
Expand All @@ -996,10 +1029,17 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
status = PHP_OUTPUT_HANDLER_FAILURE;
}
}
handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
if (still_have_handler) {
handler->flags |= PHP_OUTPUT_HANDLER_STARTED;
}
OG(running) = NULL;
}

if (!still_have_handler) {
// Handler and context will have both already been freed
return status;
}

switch (status) {
case PHP_OUTPUT_HANDLER_FAILURE:
/* disable this handler */
Expand Down Expand Up @@ -1225,6 +1265,19 @@ static int php_output_stack_pop(int flags)
}
php_output_handler_op(orphan, &context);
}
// If it isn't still in the stack, cannot free it
bool still_have_handler = false;
int handler_count = php_output_get_level();
if (handler_count) {
php_output_handler **handlers = (php_output_handler **) zend_stack_base(&OG(handlers));
for (int handler_num = 0; handler_num < handler_count; ++handler_num) {
php_output_handler *curr_handler = handlers[handler_num];
if (curr_handler == orphan) {
still_have_handler = true;
break;
}
}
}

/* pop it off the stack */
zend_stack_del_top(&OG(handlers));
Expand All @@ -1240,7 +1293,9 @@ static int php_output_stack_pop(int flags)
}

/* destroy the handler (after write!) */
php_output_handler_free(&orphan);
if (still_have_handler) {
php_output_handler_free(&orphan);
}
php_output_context_dtor(&context);

return 1;
Expand Down
1 change: 1 addition & 0 deletions sapi/cli/tests/gh8827-002.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ $stderr = fopen('php://stderr', 'r');

ob_start(function ($buffer) use ($stdout) {
fwrite($stdout, $buffer);
return '';
}, 1);

print "STDIN:\n";
Expand Down
1 change: 1 addition & 0 deletions sapi/cli/tests/gh8827-003.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ file_put_contents('php://fd/2', "Goes to stderrFile\n");

ob_start(function ($buffer) use ($stdoutStream) {
fwrite($stdoutStream, $buffer);
return '';
}, 1);

print "stdoutFile:\n";
Expand Down
2 changes: 1 addition & 1 deletion tests/output/bug60768.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Bug #60768 Output buffer not discarded

global $storage;

ob_start(function($buffer) use (&$storage) { $storage .= $buffer; }, 20);
ob_start(function($buffer) use (&$storage) { $storage .= $buffer; return ''; }, 20);

echo str_repeat("0", 20); // fill in the buffer

Expand Down
9 changes: 7 additions & 2 deletions tests/output/ob_start_basic_002.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,24 @@ foreach ($callbacks as $callback) {
}

?>
--EXPECT--
--EXPECTF--
--> Use callback 'return_empty_string':


--> Use callback 'return_false':

Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_false is deprecated in %s on line %d
My output.

--> Use callback 'return_null':

Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_null is deprecated in %s on line %d


--> Use callback 'return_string':
I stole your output.

--> Use callback 'return_zero':
0

Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_zero is deprecated in %s on line %d
0
Loading