PHP Gotchas and Tips
Here are some PHP observations that I have been caught out with and am posting so I don't get caught again. This post will grow over time as I find more bite-size nuggets.
Gotcha - MySQL Connection Converting Numbers To Strings
If you are having issues with your int/decimal/float values in your database coming out as strings in your PHP code, then you may need to set the following option when connecting to your MySQL database.
$mysqliConn->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
Whilst your at it, why not ensure UTF-8:
$db = new mysqli($host, $user, $password, $dbName, $port);
if (!$db->set_charset("utf8"))
{
printf("Error loading character set utf8: %s\n", $db->error);
die();
}
$db->options(MYSQLI_OPT_INT_AND_FLOAT_NATIVE, true);
Gotcha print_r vs var_dump
I was recently having an issue where array_diff was not working as I was expecting when comparing process_ids.
This was because I did not realized that one of the arrays have whitespace before the values. Unfortunately, this did not appear in the CLI when I was print_r($array, true)
everything as I was going along.
Swapping out print_r for var_dump immediately revealed the issue as whitespace does appear in var_dump.
Gotcha: Array Merge
Guess what the output to this code will be:
<?php
$lookup_table = array();
$lookup_table['hello'] = array();
$lookup_table['hello'][] = 1;
$lookup_table['hello'][] = 2;
$my_array1 = array();
$my_array1 = array_merge($my_array1, $lookup_table['hello']);
$my_array1 = array_merge($my_array1, $lookup_table['world']);
$my_array1 = array_merge($my_array1, $lookup_table['hello']);
print_r($my_array1);
The answer is nothing. Not only does the script not throw an error, but the merged array loses all elements that it was holding at the point of passing a null value as a parameter, as well as all future elements that would be merged after that call. If you manually run the script, you may see some notices and warnings, but you probably won't see these if the script is called by another script, or if your php ini settings are set to not shown warnings/notices (production settings).
Gotcha isset on array
You may use something like below to check if your index is in an array:
isset($row[$myFieldName])
However, this will not return the correct value if the index does exist, but the value is set to null (common with results from a database). Instead, use array_key_exists()
Gotcha - readline()
I write interactive PHP scripts all the time for CLI tools. This often requires getting a variable from the user using readline. An example piece of code would be:
$search_string = readline('Enter the string of text you wish to replace: ' . PHP_EOL);
The PHP_EOL
at the end is so that the user inputs the answer on a new line but this doesn't work! Instead you need to do this:
print 'Enter the string of text you wish to replace: ' . PHP_EOL;
$search_string = readline();
Gotcha - array_filter
And Values Of 0
You may wish to use array_filter to remove null elements from your array. Unfortunately, this will also strip out any values that are legitimately set to 0. You may need to provide your own filter closure to the function in order to resolve this.
Gotcha - array_filter
before JSON encoding
If you use array_filter to strip out elements from your array before passing it to json_encode, you may be surprised to see that you suddenly get an object out of the json_encode, rather than an array. This is because the array_filter will maintain indexes, so the json_encode will now output numerical indexes rather than outputting an array list.
To resolve this, wrap the operation in a call to array_values to re-index the array.
$outputArray = array_values(array_filter($inputArray, $filterFunc));
print json_encode($outputArray, JSON_PRETTY_RPINT);
Alternatively, you could force it to always output in object form, to keep the consisten interface, by using the JSON_FORCE_OBJECT
option in the json_encode.
Gotcha: NULL Values in CSV
The fputcsv() function will treat null values like an empty string, so if you are importing/exporting data, things can get lost in translation. To work around this, it may be easiest to export/import to JSON files instead.
Tip: empty($array)
or count($array)==0
At one point, I was using empty()
to "quickly" check if an array hadn't been given any values. This is because I didn't know at the time that the count()
function is actually O(1) and not O(n) like I had assumed (i.e. it doesn't actually loop through the elements and count them). Thus you can use them interchangeably.
Types
is_int()
will not evaluate true for a value of "20"
, however is_numeric
would. Remember that all $_POST
values, as well as values retrieved from MySql are actually in string form unless you, or your libraries, have done this for you.
Floating Point Values
Anything that requires precision and involves decimal places can lead to pain. This is because PHP does not have a double type like Java. Instead it only has integer and float types internally.
Issues can arise without doing anything complicated. For example here is an issue that arises from simple basic addition.
$myVal=0;
for($i = 0; $i<1000000; $i++)
{
$myVal += 0.1;
}
echo $myVal; //100000.00000133
echo floor((0.1+0.7)*10); // outputs 7 not 8
Here is a great explanation of what is going on:
Helpful Workarounds
Some tips from the PHP manual:
- Never trust floating number results to the last digit.
- Do not compare floating point numbers directly for equality.
Using BCMATH
Using bcmath may resolve your issues. For example, here is the previous example using bcmath:
echo floor(bcadd(0.1, 0.7,1)*10); // outputs 8 "correctly"
bcdiv(100,11,1);
will give you 9.0
, not 9.1
which is closer in value to the actual answer.bscale($precision)
.
Regular Expressions
Use Single Quotes
As a rule of thumb, use single quotes ('
) on the patterns for regular expresssions. This will save you headaches when you use the $
symbol for specifying the end of the pattern as shown below.
$pattern = "/^[0-9]*$/"
$
symbol within double quotes tries to evaluate a variable.
Start and End Characters
Don't forget that you need to specify a character for the start and end of your regular expression pattern when passing to preg_match
etc.
Although you can use any character to indicate the start and end of the regular expression, as a rule of thumb, use (/
).
This is because most on-line examples use this and lots of characters such as |
have special meanings in Regexps that you may need to use (in this case OR).
$pattern = '/^[0-9]*$/'
Generating CSV Files (Excel)
Excel will pop up with an error if the first bit of text in your generated CSV file is "ID" which is often the case if you are dumping a database table.
Gotcha - Callback Buffer - Use A Reference
When handling large files, I like to pass a callback to my Filesystem::fileWalk static function, which allows me to parse the file line-by-line, preventing me from having to load the whole thing into memory
$insertRows = [];
$callback = function($ipAddress) use(&$insertRows, $db) {
$insertRows[] = [
'ip_address' => $ipAddress,
'reason' => 'In the maravento/blackip blacklist.',
];
if (count($insertRows) >= 10000)
{
// insert the rows into the database here...
// reset the buffer
$insertRows = array();
}
};
if (count($insertRows) >= 1)
{
// insert leftovers...
}
Filesystem::fileWalk(__DIR__ . '/assets/blackip.txt', $callback);
You will have noticed that I used a reference on the $insertRows
variable. E.g.
$callback = function($ipAddress) use(&$insertRows, $db) {
This is because without this, $insertRows
will be a copy of the array at the point of declaring the callback, meaning it will be empty when within the body of the callback.
By passing a reference instead we passed a reference, so the buffer will actually be useful, by filling up and flushing out to the database every 10,000 rows.
References
First published: 16th August 2018