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.