Using PHP Comparison and Equality Operators in MODX

Making sure your code tests for equality properly


In the previous article, we looked at how to create and fire (invoke) your own custom MODX system events in your code. In this one, we'll look at using the PHP equality and comparison operators in MODX. The title of this article, and the previous sentence are a little misleading because the equality/inequality operators are a subset of the comparison operators. I wanted to make sure the article could be found with searches for either term.


MODX logo

Common Comparisons in MODX

In writing MODX snippets and plugins, you often want to retrieve a MODX object, like a resource, or user, and compare one or more of its fields against some value. For example, you might want to execute a plugin only if the current resource has a particular parent ID (e.g., 12). At the top of the plugin, you'd check the resource's parent and return immediately if the parent is not 12 so that the rest of the code in the plugin doesn't execute.

In order to do this properly, it's important to understand how the comparison and equality/inequality operators work. We'll be seeing a lot of equality tests in the following articles, so this is a good time to look at what those operators are and how they work.


Comparison Operators

We won't be looking at the new "spaceship" (<==>) or "null coalescing" (??) operators available in PHP 7 (cool as they are), since at this time, MODX does not have PHP 7 as a requirement. In fact, we're going to focus on the equality operators. For a more detailed look at the possible operators, see this PHP Manual Page.

Let's look at the example we saw earlier. Imagine that you want to execute a plugin only if the current resource has a particular parent ID (e.g., 12). At the top of the plugin, you'd check the resource's parent and return immediately if the parent is not 12 so that the rest of the code in the plugin doesn't execute. That can be done with one of the four equality operators:

  • == — equality
  • === — strict equality
  • != — inequality
  • !== — strict inequality

The results of these tests are always boolean (true or false). They're mainly used in if statements. The regular (non-strict) operators only care about the two values being compared. The strict operators, on the other hand, also consider the type of the variables (e.g., strings and numbers). The strict equality operator will only consider the values to be equal if the values are the same *and* are both of the same type.

PHP is called a "loosely typed" language because it can tolerate differences in variable types. In many circumstances, it will convert variables to another type to make a comparison. The strict versions are available in case you don't want that to happen. The conversions occur behind the scenes, sometimes when you least expect them, and always without any notice except for the failure of your code to work they way you think it does.

Here's a simple example:

$a = 12; /* integer */
$b = "12"; /* string */

/* Comparisons */
$a == $b /* (non-strict) true */
$a === $b /* (strict) false */
$a != $b /* (non-strict) false */
$a !== $b /* (non-strict) true */

In the first comparison in the example above, the value of $b is converted (the technical term is "coerced") to an integer before the comparison is made. In fact, whenever two numeric values are compared, they are converted to numbers before the comparison. In the second comparison, the conversion is not made and the equality test fails because the two variables are not of the same type. One is a string, the other is an integer.

In the third (inequality) comparison, the variables are considered equal so the inequality test fails because they both hold the same value. In for fourth (strict inequality) test, the two are not considered equal because they are not of the same type.

The automatic conversion of strings to numbers will only happen if the string variable contains numbers rather than letters. 12 == "twelve" will never be true, though strangely, 12 == "12and_some_text" is true. 12 == 'some_text12' and 12 == 'some_text12more_text' will both be false. The conversion only occurs if the string starts with a numeric character (or scientific notation — "10" == "1e1" is true).

Why Do I Care?

There are a number of ways to go wrong with comparisons. One happens when a type conversion happens that you are not expecting. If you compare an integer and a non-numeric string, you can get an unexpected result. Consider this comparison:

$x = (0 == "hello");

You might expect that the value of $x would be false. It's not. Because one of the values is a number, both sides are converted to numbers. "hello" is converted to it's numeric value (0) and the two are considered equal. Using the strict operator won't help because the two sides will never be equal, so there's no point in doing the test — 0 === "0" is still false. Reversing the parts is no help either — $x = 'hello' == 0; is also true.

Suppose your snippet code calls another snippet with $modx->runSnippet(). The snippet returns a number so you test it with this code:

$value = $modx->runSnippet('MyOtherSnippet');
if ($value === 100) {
    /* Do something */
}

Your "Do Something" code will never execute because MODX snippets always return strings. Because the two values are not of the same type, the strict comparison will always return false. Any bit of data returned from an HTML form is also guaranteed to be a string, even if the entry is a number with no quotes.

You can also go wrong when you forget what type of variable is returned from a get() call. Suppose that you want to check whether the current resource is at the root of the Resources tree. You know that the parent field of resources at the root holds a 0, but it's 3:00 am and you use this code:

if ($modx->resource->get('parent') === '0') {}

The test will never pass because in this case get() is going to return an integer, which is not the same type as the string '0'.

Here's a particularly insidious example. Suppose you create a Template Variable (TV) called numberTV. On the "Input Options" tab, you set the Input Type to "number." For a particular resource (ID = 33), you set the value of the TV to 12. Then you create this code in a snippet or plugin:

$doc = $modx->getObject('modResource', 33);
$tvValue = $doc->getTVValue('MyTV');
if ($tvValue === 12) {
    /* Do Something */
}

Again, your "Do Something" code will never execute because getTVValue() will return a string, even for number TVs that contain a number.

Imagine that you write a function that returns a string if successful and false if it fails. This code will cause you headaches if your function ever returns an empty string or a 0, because with a non-strict comparison both of those are equal to false:

$retVal = myFunction();
if ($retVal == false) {
    /* Handle Error */
} else {
    /* Do Something */
}

If false has a special meaning in your code, always test it with the strict operator: if ($retVal === false){}.


Casts

Sometimes, you know what you're getting back from a MODX method. For example, $modx->context->get('key') and $resource->get('context_key') will always return strings, so you can test them using the strict comparison operator (e.g., $modx->context->get('key') === 'web'). When you get a resource's id or parent fields, you'll get an integer. Sometimes, as we saw above with getTVValue() with a number TV, you might not be sure.

In PHP, you can do your own conversion using the cast operator (). For example, $x = (integer) '123'; will make sure $x is an integer. On the other hand $x = (string) 123; will make sure $x is a string. You can also use (int) as a shorthand for integer conversions.

When you're not sure what type you're getting back, you can always use a cast to convert the return value to whatever you need. For example, this would solve our problem above with integer TVs:

$doc = $modx->getObject('modResource', 33);
$tvValue = (int) $doc->getTVValue('MyTV');
if ($tvValue === 12) {
    /* Do Something */
}

By casting the TV value to an integer, you can use the strict equality operator and be confident that it will work. In fact, it's a good idea in this case to use the cast even if you know that you're getting a string. That way, your code will still work if sometime down the road, someone decides to make number TVs return an integer. It's safe to assume that context keys will always be strings and that snippets will always return strings, but when in doubt, use a cast. It only takes a few milliseconds and it will prevent some bugs that are extremely difficult to track down.


Why use ===?

Several of the problems we saw above could be solved by using the non-strict operators, (== and !==), so it's tempting to use the non-strict operators all the time. There are a couple of reasons not to do this, though. One is that the strict operators are faster, since no conversions need to be performed. That's not a great reason, because the time differences are so small. A better reason is the unpleasant surprises that arise from unexpected type coercions. Here are some examples (the cases below use single quotes, but the results would be the same for double quotes):

12 == '12andSomeText' /* true */
'12' == '12andSomeText' /* false */
'12' == ' 12' /* true */
' 12' == '12' /* true */
12 == 012 /* false */
'12.0' == '12' /* true */
false == 0 /* true */
false === 0 /* false */
'1e3' == '1000' /* true */
'0x10' == '16' /* false */
0x10 == 16 /* true */
false == 0 /* true */
false == '0' /* true */
false == array() /* true */
NAN == NAN /* false */
NAN == true /* true */

/* Consider the next three together */
'0'== 0 /* true */
0 == '' /* true */
'0' == '' /* false (wtf?) */

Another reason to avoid the non-strict comparison operators is that tools that analyze the quality of your code, and possibly other people who may work with your code will complain. A good code editor will often flag non-strict operators with something like this: "Possible unwanted type conversion."

For a more exhaustive (and exhausting) look at the ins and outs of the PHP equality/inequality operators, see this StackOverflow discussion.


Playing it Safe

To avoid unpleasant surprises, use the strict comparison operators whenever you can, and either be very sure the types are the same, or use a cast to make *sure* they're the same. Casts are extremely fast in PHP, so the performance penalty is negligible.


Coming Up

In the next article, we'll look at how to make the code of a plugin execute only in specific contexts.



Looking for high-quality, MODX-friendly hosting? As of May 2016, Bob's Guides is hosted at A2 hosting. (More information in the box below.)



Comments (0)


Please login to comment.

  (Login)