Skip to content

Commit 0932cef

Browse files
Deprecate producing output in a user output handler
https://wiki.php.net/rfc/deprecations_php_8_4
1 parent 64e2832 commit 0932cef

20 files changed

+1018
-11
lines changed

UPGRADING

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,12 @@ PHP 8.5 UPGRADE NOTES
265265
it is visible; if there are nested output handlers the next one will still
266266
be used.
267267
RFC: https://wiki.php.net/rfc/deprecations_php_8_4
268+
. Trying to produce output (e.g. with `echo`) within a user output handler
269+
is deprecated. The deprecation warning will bypass the handler with the bad
270+
return to ensure it is visible; if there are nested output handlers the next
271+
one will still be used. If a user output handler returns a non-string and
272+
produces output, a single combined deprecation message is used.
273+
RFC: https://wiki.php.net/rfc/deprecations_php_8_4
268274

269275
- Hash:
270276
. The MHASH_* constants have been deprecated. These have been overlooked

main/output.c

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,13 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
934934
return PHP_OUTPUT_HANDLER_FAILURE;
935935
}
936936

937+
/* php_output_lock_error() doesn't fail for PHP_OUTPUT_HANDLER_WRITE but
938+
* anything that gets written will silently be discarded, remember that we
939+
* tried to write so a deprecation warning can be emitted at the end. */
940+
if (context->op == PHP_OUTPUT_HANDLER_WRITE && OG(active) && OG(running)) {
941+
handler->flags |= PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT;
942+
}
943+
937944
bool still_have_handler = true;
938945
/* storable? */
939946
if (php_output_handler_append(handler, &context->in) && !context->op) {
@@ -962,14 +969,27 @@ static inline php_output_handler_status_t php_output_handler_op(php_output_handl
962969
handler->func.user->fci.retval = &retval;
963970

964971
if (SUCCESS == zend_call_function(&handler->func.user->fci, &handler->func.user->fcc) && Z_TYPE(retval) != IS_UNDEF) {
965-
if (Z_TYPE(retval) != IS_STRING) {
972+
// Use a single deprecation message for both types of deprecation
973+
// * Returning a non-string
974+
// * Trying to produce output
975+
const char *error_msg = NULL;
976+
if (Z_TYPE(retval) != IS_STRING && handler->flags & PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT) {
977+
error_msg = "Returning a non-string result or producing output from user output handler %s is deprecated";
978+
} else if (Z_TYPE(retval) != IS_STRING) {
979+
error_msg = "Returning a non-string result from user output handler %s is deprecated";
980+
} else if (handler->flags & PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT) {
981+
error_msg = "Producing output from user output handler %s is deprecated";
982+
}
983+
// The handler might not always produce output
984+
handler->flags &= ~PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT;
985+
if (error_msg != NULL) {
966986
// Make sure that we don't get lost in the current output buffer
967987
// by disabling it
968988
handler->flags |= PHP_OUTPUT_HANDLER_DISABLED;
969989
php_error_docref(
970990
NULL,
971991
E_DEPRECATED,
972-
"Returning a non-string result from user output handler %s is deprecated",
992+
error_msg,
973993
ZSTR_VAL(handler->name)
974994
);
975995
// Check if the handler is still in the list of handlers to

main/php_output.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#define PHP_OUTPUT_HANDLER_STARTED 0x1000
4343
#define PHP_OUTPUT_HANDLER_DISABLED 0x2000
4444
#define PHP_OUTPUT_HANDLER_PROCESSED 0x4000
45+
#define PHP_OUTPUT_HANDLER_PRODUCED_OUTPUT 0x8000
4546

4647
#define PHP_OUTPUT_HANDLER_ABILITY_FLAGS(bitmask) ((bitmask) & ~0xf00f)
4748

tests/output/ob_start_callback_bad_return/exception_handler.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
ob_start(): Check behaviour with deprecation converted to exception
2+
ob_start(): Check behaviour with deprecation converted to exception [bad return]
33
--FILE--
44
<?php
55

tests/output/ob_start_callback_bad_return/exception_handler_nested.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
ob_start(): Check behaviour with deprecation converted to exception
2+
ob_start(): Check behaviour with nested deprecation converted to exception [bad return]
33
--FILE--
44
<?php
55

tests/output/ob_start_callback_bad_return/multiple_handlers.phpt

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
--TEST--
2-
ob_start(): Check behaviour with multiple nested handlers with had return values
2+
ob_start(): Check behaviour with multiple nested handlers with bad return values
33
--FILE--
44
<?php
55

@@ -69,21 +69,30 @@ echo "\n\nLog:\n";
6969
echo implode("\n", $log);
7070
?>
7171
--EXPECTF--
72+
Deprecated: ob_end_flush(): Producing output from user output handler return_given_string is deprecated in %s on line %d3
73+
74+
Deprecated: ob_end_flush(): Producing output from user output handler return_empty_string is deprecated in %s on line %d2
75+
76+
7277
Log:
7378
return_zero: <<<Testing...>>>
7479
return_string: <<<
7580
Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_zero is deprecated in %s on line %d
7681
0>>>
77-
return_null: <<<I stole your output.>>>
82+
return_null: <<<
83+
Deprecated: ob_end_flush(): Producing output from user output handler return_string is deprecated in %s on line %d
84+
I stole your output.>>>
7885
return_true: <<<
79-
Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_null is deprecated in %s on line %d
86+
Deprecated: ob_end_flush(): Returning a non-string result or producing output from user output handler return_null is deprecated in %s on line %d
8087
>>>
8188
return_false: <<<
82-
Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_true is deprecated in %s on line %d
89+
Deprecated: ob_end_flush(): Returning a non-string result or producing output from user output handler return_true is deprecated in %s on line %d
8390
>>>
8491
return_empty_string: <<<
85-
Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_false is deprecated in %s on line %d
92+
Deprecated: ob_end_flush(): Returning a non-string result or producing output from user output handler return_false is deprecated in %s on line %d
8693

87-
Deprecated: ob_end_flush(): Returning a non-string result from user output handler return_true is deprecated in %s on line %d
94+
Deprecated: ob_end_flush(): Returning a non-string result or producing output from user output handler return_true is deprecated in %s on line %d
95+
>>>
96+
return_given_string: <<<
97+
Deprecated: ob_end_flush(): Producing output from user output handler return_empty_string is deprecated in %s on line %d2
8898
>>>
89-
return_given_string: <<<>>>
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
--TEST--
2+
ob_start(): Check behaviour with deprecation converted to exception [produce output]
3+
--FILE--
4+
<?php
5+
6+
$log = [];
7+
8+
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) {
9+
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
10+
});
11+
12+
function first_handler($string) {
13+
global $log;
14+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
15+
echo __FUNCTION__;
16+
return "FIRST\n";
17+
}
18+
19+
function second_handler($string) {
20+
global $log;
21+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
22+
echo __FUNCTION__;
23+
return "SECOND\n";
24+
}
25+
26+
function third_handler($string) {
27+
global $log;
28+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
29+
echo __FUNCTION__;
30+
return "THIRD\n";
31+
}
32+
33+
$cases = [
34+
'first_handler',
35+
'second_handler',
36+
'third_handler',
37+
];
38+
foreach ($cases as $case) {
39+
$log = [];
40+
echo "\n\nTesting: $case\n";
41+
ob_start($case);
42+
echo "Inside of $case\n";
43+
try {
44+
ob_end_flush();
45+
} catch (\ErrorException $e) {
46+
echo $e . "\n";
47+
}
48+
echo "\nEnd of $case, log was:\n";
49+
echo implode("\n", $log);
50+
}
51+
52+
?>
53+
--EXPECTF--
54+
Testing: first_handler
55+
FIRST
56+
ErrorException: ob_end_flush(): Producing output from user output handler first_handler is deprecated in %s:%d
57+
Stack trace:
58+
#0 [internal function]: {closure:%s:%d}(8192, 'ob_end_flush():...', %s, 41)
59+
#1 %s(%d): ob_end_flush()
60+
#2 {main}
61+
62+
End of first_handler, log was:
63+
first_handler: <<<Inside of first_handler
64+
>>>
65+
66+
Testing: second_handler
67+
SECOND
68+
ErrorException: ob_end_flush(): Producing output from user output handler second_handler is deprecated in %s:%d
69+
Stack trace:
70+
#0 [internal function]: {closure:%s:%d}(8192, 'ob_end_flush():...', %s, 41)
71+
#1 %s(%d): ob_end_flush()
72+
#2 {main}
73+
74+
End of second_handler, log was:
75+
second_handler: <<<Inside of second_handler
76+
>>>
77+
78+
Testing: third_handler
79+
THIRD
80+
ErrorException: ob_end_flush(): Producing output from user output handler third_handler is deprecated in %s:%d
81+
Stack trace:
82+
#0 [internal function]: {closure:%s:%d}(8192, 'ob_end_flush():...', %s, 41)
83+
#1 %s(%d): ob_end_flush()
84+
#2 {main}
85+
86+
End of third_handler, log was:
87+
third_handler: <<<Inside of third_handler
88+
>>>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--TEST--
2+
ob_start(): Check behaviour with nested deprecation converted to exception [produce output]
3+
--FILE--
4+
<?php
5+
6+
$log = [];
7+
8+
set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) {
9+
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
10+
});
11+
12+
function first_handler($string) {
13+
global $log;
14+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
15+
echo __FUNCTION__;
16+
return "FIRST\n";
17+
}
18+
19+
function second_handler($string) {
20+
global $log;
21+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
22+
echo __FUNCTION__;
23+
return "SECOND\n";
24+
}
25+
26+
function third_handler($string) {
27+
global $log;
28+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
29+
echo __FUNCTION__;
30+
return "THIRD\n";
31+
}
32+
33+
ob_start('first_handler');
34+
ob_start('second_handler');
35+
ob_start('third_handler');
36+
37+
echo "In all of them\n\n";
38+
try {
39+
ob_end_flush();
40+
} catch (\ErrorException $e) {
41+
echo $e->getMessage() . "\n";
42+
}
43+
echo "Ended third_handler\n\n";
44+
45+
try {
46+
ob_end_flush();
47+
} catch (\ErrorException $e) {
48+
echo $e->getMessage() . "\n";
49+
}
50+
echo "Ended second_handler\n\n";
51+
52+
try {
53+
ob_end_flush();
54+
} catch (\ErrorException $e) {
55+
echo $e->getMessage() . "\n";
56+
}
57+
echo "Ended first_handler handler\n\n";
58+
59+
echo "All handlers are over\n\n";
60+
echo implode("\n", $log);
61+
62+
?>
63+
--EXPECT--
64+
FIRST
65+
ob_end_flush(): Producing output from user output handler first_handler is deprecated
66+
Ended first_handler handler
67+
68+
All handlers are over
69+
70+
third_handler: <<<In all of them
71+
72+
>>>
73+
second_handler: <<<THIRD
74+
ob_end_flush(): Producing output from user output handler third_handler is deprecated
75+
Ended third_handler
76+
77+
>>>
78+
first_handler: <<<SECOND
79+
ob_end_flush(): Producing output from user output handler second_handler is deprecated
80+
Ended second_handler
81+
82+
>>>
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
--TEST--
2+
ob_start(): Check behaviour with functions that trigger output (nested)
3+
--FILE--
4+
<?php
5+
6+
$log = [];
7+
8+
function handle_echo($string) {
9+
global $log;
10+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
11+
echo __FUNCTION__;
12+
return "echo\n";
13+
}
14+
15+
function handle_var_dump($string) {
16+
global $log;
17+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
18+
var_dump(__FUNCTION__);
19+
return "var_dump\n";
20+
}
21+
22+
function handle_var_export($string) {
23+
global $log;
24+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
25+
var_export(__FUNCTION__);
26+
return "var_export\n";
27+
}
28+
29+
function handle_phpcredits($string) {
30+
global $log;
31+
$log[] = __FUNCTION__ . ": <<<" . $string . ">>>";
32+
phpcredits();
33+
return "phpcredits\n";
34+
}
35+
36+
$cases = [
37+
'handle_echo',
38+
'handle_var_dump',
39+
'handle_var_export',
40+
'handle_phpcredits',
41+
];
42+
foreach ($cases as $case) {
43+
$log = [];
44+
echo "\n\nTesting: $case";
45+
ob_start($case);
46+
echo "Inside of $case\n";
47+
ob_end_flush();
48+
echo "\nEnd of $case, log was:\n";
49+
echo implode("\n", $log);
50+
}
51+
52+
?>
53+
--EXPECTF--
54+
Testing: handle_echo
55+
Deprecated: ob_end_flush(): Producing output from user output handler handle_echo is deprecated in %s on line %d
56+
echo
57+
58+
End of handle_echo, log was:
59+
handle_echo: <<<Inside of handle_echo
60+
>>>
61+
62+
Testing: handle_var_dump
63+
Deprecated: ob_end_flush(): Producing output from user output handler handle_var_dump is deprecated in %s on line %d
64+
var_dump
65+
66+
End of handle_var_dump, log was:
67+
handle_var_dump: <<<Inside of handle_var_dump
68+
>>>
69+
70+
Testing: handle_var_export
71+
Deprecated: ob_end_flush(): Producing output from user output handler handle_var_export is deprecated in %s on line %d
72+
var_export
73+
74+
End of handle_var_export, log was:
75+
handle_var_export: <<<Inside of handle_var_export
76+
>>>
77+
78+
Testing: handle_phpcredits
79+
Deprecated: ob_end_flush(): Producing output from user output handler handle_phpcredits is deprecated in %s on line %d
80+
phpcredits
81+
82+
End of handle_phpcredits, log was:
83+
handle_phpcredits: <<<Inside of handle_phpcredits
84+
>>>

0 commit comments

Comments
 (0)