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.