More Facepalms
The
(unset)
Cast
This is a fun one. For whatever reasons, probably just because in some way PHP’s grammar allowed it, and just because the compiler would not bail out on it, the following code is valid PHP syntax:
$a = 42;
var_dump($a);
(unset) $a;
var_dump($a);
Yes, it was possible to cast a value to
unset
. Which is not the same thing as unsetting it,
because it just returns null
. It does not affect the
original variable, as the output shows:
int(42)
int(42)
Just for the sake of completeness: if you want to unset a
variable, use unset()
:
$a = 42;
var_dump($a);
unset($a);
var_dump($a);
This will unset the variable as expected:
int(42)
Notice: Undefined variable: a ...
NULL
Since casting to unset
always returns
null
, it is just a very fancy way of writing
null
itself. The whole thing just serves no purpose.
Thus, as of PHP 7.2, casting to unset
has been
deprecated:
Deprecated: The (unset) cast is deprecated in ...
If you encounter this error message, fixing up the code is really
easy. Just remove the cast to unset
, as it does not do
anything anyway.
Deprecated
ezmlm_hash()
ezmlm (“eazy mailing list manager”) is a free mailing list software developed in 1997 that has been unmaintained ever since. A fork exists that is named ezmlm-idx, but this has also been unmaintained since 2014.
The function ezmlm_hash()
can be used to hash an
email address. According to the manual this is required when storing
ezmlm mailing lists in a MySQL database. As of PHP 7.4,
ezmlm_hash()
has been deprecated, so
var_dump(ezmlm_hash('user@example.com'));
will result in:
PHP Deprecated: Function ezmlm_hash() is deprecated in ...
int(44)
You are asking why this is a facepalm? Because it breaks encapsulation if you deal with implementation details that should, by definition, be private in the first place. If ezmlm at some point decided to change the way they hash an email address, they would most certainly not be aware of the fact that they would need to tell the PHP project.
Plus, a hash should not be an int, should it?
Nested Ternary Operators Need Parentheses
Computer programs are mainly about loops, and about making
decisions. We use if
statements to make decisions, but
sometimes, when we feel that the code is too verbose, we might use
the ternary operator instead:
$a = random_int(0, 10);
if ($a > 5) {
$result = 'pretty big';
} else {
$result = 'rather small';
}
We can shorten this to:
$result = $a > 5 ? 'pretty big' : 'rather small';
One can argue whether it is worth shortening this if
statement to one line, since code coverage reporting would show that
one line as executed, event though there are technically two
execution paths on this one line, requiring two test cases to be
fully covered. The more verbose if statement puts execution path on
a single line, which makes code coverage reporting less
misleading.
But this is –maybe– besides the point here. How about this piece of code:
$result = $a > 5 ? 'pretty big' : $a < 1 ? 'very small' : 'rather small';
This line is hard to read. Maybe the ternary operator should just not be nested? Still, nested ternary operators are allowed in PHP. It turns out that PHP interprets nested ternary operators differently, namely left-associative. Most other programming langauge treat it right-associatively, which of course causes a lot of confusion:
$a = 3;
var_dump(
$a == 1 ? 'one'
: $a == 2 ? 'two'
: $a == 3 ? 'three'
: $a == 4 ? 'four'
: 'other'
);
Now, what output would you expect?
string(4) "four"
That is a surprise, isn’t it? Again, maybe you should not have nested ternary operators in the first place, but since you did, PHP now helps you with a deprecation message:
PHP Deprecated:
Unparenthesized a ? b : c ? d : e is deprecated.
Use either (a ? b : c) ? d : e or a ? b : (c ? d : e) in ...
Basically this tells you to make the operator precedence explicit:
$a = 3;
var_dump(
$a == 1 ? 'one'
: ($a == 2 ? 'two'
: ($a == 3 ? 'three'
: ($a == 4 ? 'four'
: 'other')))
);
Can we please agree on not using nested ternary operators?
Non-String Arguments to
mb_ereg_replace()
When working with strings, using regular expressions to search
and replace is very common, and sometimes extremely useful. We have
already mentioned previously that PHP’s built-in string functions do
a pretty bad job on non-ASCII strings. The mbstring
extension offers functions that can deal with multibyte strings, for
example mb_ereg_replace()
that can be used to replace
substrings:
var_dump(mb_ereg_replace('ä', 'ö', 'seriäs'));
Even though this example does not use a “real” regular
expression, it does use German umlauts and thus non-ASCII
characters. To do things properly, we would have to call
mb_regex_encoding()
to define the encoding. But since
we are in a “facepalm” chapter, we do not need all this to let
things get weird. What would you expect the following example to
do:
var_dump(mb_ereg_replace(111, '...', 'some string'));
Well, let us run the code to find out:
PHP Deprecated:
mb_ereg_replace(): Non-string patterns will be interpreted as strings in the future.
Use an explicit chr() call to preserve the current behavior in ...
string(13) "s...me string"
PHP treats the integer 111 as the ASCII character o
,
thus replacing it in the original string. Since PHP 7.4, however,
there is an additional deprecation message which in this case warns
you of a future change to PHP: probably starting with PHP 8, the
integer 111
will be interpreted as a string
'111'
, which will probably lead to unexpected
behaviour. But then again, there should really be no reason to pass
a non-string argument to a function that makes string
replacements.
Case-Insensitive Constants
Constants are always upper case, right? Well … almost. Turns out
that in PHP it has always been more of a soft convention that
constants are upper case. The special constants true
,
false
, and null
, for example, also exist
in a lower case version, and in fact most coding standards suggest
to use them in lower case only.
define('TEST_Constant', 'some-value', true);
var_dump(TEST_CONSTANT);
This will output:
Warning: Use of undefined constant TEST_CONSTANT - assumed 'TEST_CONSTANT'
(this will throw an Error in a future version of PHP) in ...
string(13) "TEST_CONSTANT"
This is clearly not case-sensitive, but exposes PHP’s strange
behaviour of defining constants at runtime. To add even more
confusion, you can make constants case-insensitive by passing an
additional parameter to define()
:
define('TEST_Constant', 'some-value', true);
var_dump(TEST_CONSTANT);
On older PHP versions, this will result in:
string(10) "some-value"
Since PHP 7.3, two deprecation warnings will be shown, one when defining a case-insensitive constant
Deprecated:
define():
Declaration of case-insensitive constants is deprecated in ...
Deprecated:
Case-insensitive constants are deprecated.
The correct casing for this constant is "TEST_Constant" in ...
string(10) "some-value"
In PHP 8, it will not be possible to define case-insensitive constants any more. In addition, PHP 8 will no longer magically define constants at runtime, so all of the above programs will lead to a fatal error on PHP 8.
Bottom line: just always use upper case constants. Start doing so now, if you haven’t already. If you are not convinced yet:
define('constant', 'some-value', true);
var_dump(constant);
var_dump(CONSTANT);
define('CONSTANT', 'some-other-value');
var_dump(constant);
var_dump(CONSTANT);
Up to PHP 7.2.31, this will output:
string(10) "some-value"
string(10) "some-value"
string(10) "some-value"
string(16) "some-other-value"
Whoops! Did we just re-define a constant?
Impossible URL Filters
Using the filter
extension, input strings can be
filtered (or validated), for example to find out if a given string
is a valid URL:
var_dump(filter_var('http://example.com/', FILTER_VALIDATE_URL));
var_dump(filter_var('not-a-url', FILTER_VALIDATE_URL));
Obviously, the first string is a valid URL, whereas the second one is not:
string(19) "http://example.com/"
bool(false)
Since PHP 7.3, the two constants
FILTER_FLAG_SCHEME_REQUIRED
and
FILTER_FLAG_HOST_REQUIRED
are deprecated.
var_dump(filter_var('//example.com/', FILTER_VALIDATE_URL, FILTER_FLAG_SCHEME_REQUIRED));
var_dump(filter_var('https:///path/', FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED));
Not only will PHP yell at you, the constants do not serve any purpose since neither the scheme, nor the host can be omitted from the URL:
PHP Deprecated:
filter_var():
explicit use of FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED is deprecated in ...
bool(false)
PHP Deprecated:
filter_var():
explicit use of FILTER_FLAG_SCHEME_REQUIRED and FILTER_FLAG_HOST_REQUIRED is deprecated in ...
bool(false)
Since the two constants in question do not serve any purpose,
they have been deprecated and will be removed in PHP 8. If your
application is affected it should be safe just to remove the
constants from the filter_var()
call.
Are you Using PDO to Connect to DB2 via ODBC?
This sounds like a pretty edgy edge case already. If you
additionally use the php.ini
setting
pdo_odbc.db2_instance_name
, you will be greeted with a
deprecation warning starting with PHP 7.3.
The fun fact is that this setting has been marked as deprecated
in the PHP manual since version 5.1.1. Now the code was adjusted to
the manual, if you will. The reason for the deprecation is that
pdo_odbc.db2_instance_name
triggered the modification
of a system environment variable, which could cause undesired side
effects.