I/O and Filesystem

Accessing Raw POST Data

PHP powers most existing web sites on the world wide web. Web mostly answer to GET and POST requests, maybe also HEAD requests. GET should query the server state, and only POST should change the server’s state. Both request types also differ in the way parameters are passed. While GET parameters are appended to the request URL, POST parameters are passed in the request body. The HTTP standards also allow appending GET parameters to the URLs of POST requests, even if we would not encourage you to do this, because we think it is bad style.

When the encoding type multipart/form-data is used, a POST request can also contain encoded files. This is how browsers upload files to the server. PHP will automatically parse GET and POST requests, and lets us access the data through the $_GET, $_POST, and $_FILES super-globals.

In some cases, however, we need access to the raw post data, for example to parse JSON data that has been POSTed to our server. Earlier PHP versions used to populate the global variable HTTP_RAW_POST_DATA when the php.ini setting always_populate_raw_post_data was set to 1. This variable has already been deprecated in PHP 5.6, and has now been removed in PHP 7:

var_dump($HTTP_RAW_POST_DATA);
Notice: Undefined variable: HTTP_RAW_POST_DATA in ...
NULL

The configuration setting always_populate_raw_post_data has also been removed. If your php.ini still contains such a line, it will be ignored. Contrary to other removed configuration settings, there will be no error message or warning.

If you need to access raw post data in PHP 7, you can use the read-only stream php://input:

var_dump($_POST);
var_dump(file_get_contents('php://input'));

To test this script, we can use the built-in webserver:

php -S 127.0.0.1:8080 -s post.php

This assumes that the above script was saved as post.php. Now let us send an HTTP POST request to our script. We can use the command-line tool curl, for example:

curl -X POST --data "foo=bar" 127.0.0.1:8080

Our script post.php will output the following:

array(1) {
  ["foo"]=>
  string(3) "bar"
}
string(7) "foo=bar"

The array shows the POST parameters that PHP has automatically decoded from the request body. The string displayed below the array is the original POST body.

Hash-Sign Comments in ini Files

PHP can parse ini files, which can contain comments. A comment is a line starting a line with a semicolon. According to the ini specification, each comment must start on an individual line, and trailing comments are not allowed, as well as comments starting with a hash sign.

PHP, having a long history of not exactly sticking to standards, used to support both types of comments in version 5. PHP 7, however, does not allow trailing comments started with a hash sign. Let us assume the following ini file named configuration.ini:

; Comment
a = 1 # Comment
# Comment
b = 2; Comment

We use this script to parse the ini file:

$settings = parse_ini_file(__DIR__ . '/configuration.ini');
var_dump($settings);

This will output:

array(2) {
  ["a"]=>
  string(11) "1 # Comment"
  ["b"]=>
  string(1) "2"
}

As we can see, comment lines can be started with a semicolon, or a hash sign. Trailing comments starting with a semicolon are still possible. Trailing comments starting with a hash sign, however, will not be parsed.

The easiest fix is to edit all ini files (or ini file generators) that contain comments starting with a hash sign to use a semicolon instead. Trailing comments should not be used at all.

json_decode() with JSON_OBJECT_AS_ARRAY option

Working with JSON data is pretty common. When decoding JSON data in PHP, you can choose between getting objects (stdClass instances) or associative arrays.

The json_decode() function can take multiple arguments, and all but the first one are optional. The second parameter is a flag that determines whether PHP should decode to associative arrays rather than objects. The fourth argument is a bitfield, currently allowing two options, namely JSON_BIGINT_AS_STRING and JSON_OBJECT_AS_ARRAY. Before PHP 7.2, the latter was ignored. Since PHP 7.2, specifying JSON_OBJECT_AS_ARRAY has the same effect like setting the second argument, the so-called ‘’associative array’’’ flag to true.

This might result in a change of PHP’s behaviour, when json_decode() now returns associative arrays where it previously returned objects. The client code working with the decoded result needs to be prepared to handle the data correctly.

Safe Uploads in curl Extension

Before PHP 5.5, HTTP file uploads using the curl extension were insecure – as they allowed uploads of arbitrary files in case the value happened to start with @ – and required the use of something that hardly qualifies as an API:

curl_setopt($curl_handle, CURLOPT_POST, 1);
$args['file'] = '@/path/to/file.png';
curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $args);

PHP 5.5 replaced this with the more user-friendly and secure CurlFile API, making file uploads an explicit decision in the code rather than relying on a prefix character of the field value:

curl_setopt($curl_handle, CURLOPT_POST, 1);
$args['file'] = new CurlFile('file.png', 'image/png');
curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $args);

The old way was still supported for backwards compatibility reasons but as of PHP 5.6 disabled by default. If for any reason still needed, it could be re-enabled:

curl_setopt($curl_handle, CURLOPT_SAFE_UPLOAD, false);

As of PHP 5.5 using the outdated API already triggered an E_DEPRECATED notice. Consequently, with PHP 7 only the new API is supported, and the option to still enable the unsafe upload method got removed.

sql.safe_mode Configuration Directive

You are not going to miss this one. First of all, do not confuse the SQL Safe Mode with PHP’s Safe Mode that has already been removed from PHP in version 5.4. The original idea of the SQL Safe Mode might sound appealing: instead of keeping database credentials in the PHP source code, put them into the php.ini configuration file. The rationale behind this is that there is less risk of leaking database credentials.

While this idea may not be bad in itself, the actual PHP implementation basically prevented it from working. Only the classic mysql database extension (which has been deprecated for years), and the rarely used interbase extension actually honored the SQL Safe Mode. With all other major and modern database extensions like mysqli, oci8, or PDO, the SQL Safe mode has never worked.

Thus the SQL Safe Mode has been removed from PHP. Please note that PHP will not complain about a sql.safe_mode setting in php.ini, but will just silently ignore it. Make sure to remove any SQL Safe mode-related settings from your php.ini.

PDO, PostgreSQL, and Prepared Statements

The PostgreSQL backend for PDO before PHP 7 had two technically equivalent ways to disable the use of native prepared statements. It could be achieved by either explicitly disabling them using PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT or by telling PDO to only emulate prepared statements using ATTR_EMULATE_PREPARES:

$pdo = new PDO(...);

$pdo->setAttribute(
    PDO::PGSQL_ATTR_DISABLE_NATIVE_PREPARED_STATEMENT, true
);

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);

As this is redundant, allows for conflicting settings and is generally confusing, the PostgreSQL specific option got removed from PHP 7.

MySQLi Embedded Server

Applications, especially PHP applications that work by request rather than relying on long-running processes, have to store their state somewhere. And even if there is a wide choice of persistence mechanisms these days, relational databases are probably still the most widely used ones.

In larger setups, the database usually runs on a separate server, which makes perfect sense from a scalability point of view. In smaller setups, for example on test or staging systems, it may be preferable to run the database on the same server that executes the PHP code.

Taking this idea one step further, why run the database in a separate process? If we are able to run the database in the same (operating system) process that executes the PHP code, there is less communication overhead between code and database. In addition, setup and tear-down of tests becomes a lot easier, because the current state of the database literally just vanishes when the PHP process ends.

Those so-called embedded databases have a few interesting use-cases, however, they are no magic one-size-fits-all solutions. Quite a few well-known applications, for example Firefox, use embedded databases. In fact, the embedded database Sqlite, which also comes bundled with PHP, claims to be the most widely-used database in the world.

It seems that up to PHP 7.4, at least theoretically, one could embed a MySQL database into a PHP application and access it through the mysqli extension. We admit, however, that we learned about this feature when we read that it got removed. We have never seen anybody use that feature in production. With the increased use of containers, deploying a self-contained application is far less of an issue than it used to be.

Allegedly embedding a MySQL was broken since PHP 7.0, and the whole feature had never been documented properly anyway. So, consequently, the global functions mysqli_embedded_server_start() and mysqli_embedded_server_end() have been removed, just like the respective methods of the class mysqli_driver.

If you feel that your application needs an embedded database, use Sqlite. Be warned, however: every database speaks a different SQL dialect. Testing on an embedded database like Sqlite, but running a different database engine, for example MySQL, in production will get in you in trouble. SQL statements written for one database engine do not necessarily work on another one, so whatever your tests tell you does not necessarily hold true for your live system - or vice versa.