I/O and Filesystem

Nested Output Buffers and Callbacks

Being able to use output buffering to intercept any output generated before it is sent off to the client is a sometimes practical treat available since PHP 4 times and not new at all. PHP even supports nested stacking of output buffers:

ob_start();
echo "output #1\n";

ob_start();
echo "output #2\n";
ob_end_clean();

echo "output #3\n";
ob_flush();

Not particularly surprising and due to the fact the inner echo’s output is wrapped in a nested buffer whose value is not used, the output is suppressed:

output #1
output #3

Attempting to open another output buffer within a callback for ob_start(), though, is not supported as the following code example demonstrates:

function obHandler($buffer, $phase = NULL) {
    ob_start();
    //...
}
ob_start('obHandler');

Trying to register the example callback obHandler triggers an error:

PHP Fatal error: ob_start():
Cannot use output buffering in output buffering display handlers

This is particularly impractical if the callback does not open the nested buffer itself but uses some other code units which could also be used in a different context. Since there is no way for such code to detect it is being run as part of an output buffer handler, in previous versions of PHP the code would have had to be duplicated.

For PHP 7, the error level got downgraded from E_ERROR to E_RECOVERABLE_ERROR. This, in combination with a custom error handler, allows for a relatively simple workaround:

set_error_handler(
    function
    exception_error_handler($severity, $message, $file, $line)
    {
        throw new ErrorException(
            $message, 0, $severity,
            $file, $line
        );
    }
);

function obHandler($buffer, $phase = NULL)
{
    try {
        ob_start();
    } catch (ErrorException $e) {
        // ...
    }
    // ...
    return $buffer;
}

ob_start('obHandler');

New Implementation for JSON Encoding and Decoding

PHP 5.2 introduced support to parse JSON data structures into array or object structures – json_decode() – as well as encode those into a JSON string using json_encode(). To avoid reinventing the wheel the PHP project back then decided to bundle an implementation originally developed by json.org.

Unfortunately, that implementation came with a nonstandard license which would have caused legal issues for various Linux distributions if they were to bundle this extension. A new extension was created to mitigate this problem. It was originally hosted on the PHP Extension Community Library (PECL) at pecl.php.net and most operating system distributions packaged it instead of the original extension without the majority of developers even noticing.

With PHP 7, the code of the PECL extension – named jsond – got merged back into the core language, effectively replacing the original json.org implementation.

Along with this change of implementation, the JSON encoding and decoding is now more strict in what it accepts as input. Passing an empty string – or a value that when casted to string equals an empty string like null or false – to json_decode() is now also considered invalid:

$x = json_decode(false);
var_dump($x, json_last_error_msg());
NULL
string(12) "Syntax error"

To be RFC 7159 compliant, float representations must have at least one digit following their decimal point, a JSON string "1." is no longer considered valid:

$x = json_decode("1.");
var_dump($x, json_last_error_msg());

Running the code above with PHP 5.6 parses the value into a float:

double(1)
string(8) "No error"

With PHP 7, since the JSON string is not RFC 7159 compliant, the parsing fails:

NULL
string(12) "Syntax error"