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

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Next Next commit
Deprecate returning non-string values from a user output handler
  • Loading branch information
DanielEScherzer committed Jun 24, 2025
commit f4c3fde89284d5cf49abc88a5845d9c83907a253
6 changes: 6 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ 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 user output handlers to ensure it is
visible.
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
39 changes: 28 additions & 11 deletions main/output.c
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,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 +960,33 @@ 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 an output buffer
int old_flags = OG(flags);
OG(flags) = old_flags & (~PHP_OUTPUT_ACTIVATED);
php_error_docref(
NULL,
E_DEPRECATED,
"Returning a non-string result from user output handler %s is deprecated",
ZSTR_VAL(handler->name)
);
OG(flags) = old_flags;
}
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may fail with non-stringable objects, also I would prefer zend_try_get_string() rather than converting the ZVAL.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is unchanged from the original, I'd rather not change things

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, but can you add a test with a non-stringable object being returned from an output handler?

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 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
8 changes: 6 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,23 @@ foreach ($callbacks as $callback) {
}

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


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


--> 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
Loading