This chapter is especially important for using PHP in general, read it carefully.

PHP is a dynamically typed language, like Ruby, Python or JavaScript. That means that a variable may contain a value of any type. The type of a variable is not known until the program actually executes.

The alternative approach to that are statically typed languages where the type of a variable is defined explicitly in the code or inferred by the compiler before the program is executed. Because in statically typed languages the type of most values is already known, they can enforce that only compatible operations are performed on those values.

Take this Java program for example:

public class TypeDemo 
{
    public static void main (String[] args)
    {
        int i  = 42;
        String s = "derp";

        if (i < s) {
            System.out.println("42 is less than 'derp'.");
        }
    }
}

The important part here is:

int i  = 42;
String s = "derp";

if (i < s) {
    System.out.println("42 is less than 'derp'.");
}

The javac compiler will complain when we try to compile this because it already knows that i will be an integer and s will be a string.

TypeDemo.java:8: error: bad operand types for binary operator '<'
        if (i < s) {
              ^
  first type:  int
  second type: String
1 error

Comparing an integer to a string doesn't make sense in Java. There are pretty strong arguments that it doesn't make sense anywhere.

Even some dynamic languages refuse to execute code like this. Here's the same example in Ruby:

i = 42;
s = "derp";

if i < s
  puts "42 is less than 'derp'"
end

Executing this leads to an exception because as soon as Ruby encounters the comparison operator and realizes that the types don't match, it treats that as a problem:

typedemo.rb:4:in `<': comparison of Fixnum with String failed (ArgumentError)
    from typedemo.rb:4:in `<main>'

Let's do the same in PHP:

$i = 42;
$s = 'derp';

if ($i < $s) {
    echo "42 is less than 'derp'";
}

When we execute this program, no output is printed. But why?

PHP didn't complain about comparing two incompatible types. It did something to the 'derp' value in $s. When a value in PHP is used in a context that it normally wouldn't fit, PHP will try to convert the value into a type that is compatible with the attempted operation. In this case, we try to compare a string to an integer. PHP converts both values into integers to make the comparison possible. Since 'derp' contains no digits that could be parsed as a number, PHP converts in into 0.

Now, the comparison is 42 < 0 which is false. That's why the echo line wasn't executed.

This behavior, called "type juggling", "type coercion" or "implicit typecasting" is one of the major sources of bugs, security flaws and code maintainability issues in PHP. It's usually a good idea to avoid it.

Avoiding type juggling

Type safe equality

Most operators in PHP can cause type juggling. The main exception is ===, the type safe equality operator. It returns true if both values are of the same type and the same value and it doesn't try to convert them into anything. The == only checks for equal values AFTER implicit type casting.

When checking two non-object values for equality, always use ===.

Not only does it prevent hard to predict behavior, it also makes it clear that you want to check for real equality in your code.

There are no type safe equivalents for the other comparison operators, like <, >, <= and >=. They always cause implicit type casting, sometimes in unexpected ways. For example, it can happen, that two strings are cast into integers or float when compared with these operators. In most cases, it's only safe to use them with integers. Floats are problematic for comparison operations due to their inherent imprecision.

Comparing objects

For objects, the comparison operators behave differently. == will return true, if both values are objects of the same class and have the same values in their properties. The properties are compared using == again.

For objects, === will only return true, if both values are the exact same object.

<?php

class Foo
{
    protected $str;

    public function __construct($str)
    {
        $this->str = $str;
    }
}

$a = new Foo("42");
$b = new Foo(42);
$c = $a;

var_dump($a === $b); //prints "false", $a and $b contain different objects
var_dump($a == $b);  //prints "true", $a and $b are of the same class and have equal properties
var_dump($a === $c);  //prints "true", $a and $c contain the exact same object

The non-equal comparison operators, <, >, <= and >=, are practically unusable and undocumented on objects.

Comparing strings

When comparing strings, it can happen that PHP converts both values of a comparison into integers or floats. To avoid that, use the strcmp function.

Explicit typecasting

An alternative to letting PHP do implicit typecasting is to explicitly do it yourself. In PHP, there's a special syntax for converting types:

<?php

$i = 42;
$f = 3.14;
$s = "derp"
$b = false;

var_dump( (string)$i ); //prints 'string(2) "42"'
var_dump( (int)$f );    //prints 'int(3)'
var_dump( (bool)$s );   //prints 'bool(true)'
var_dump( (float)$b );  //prints 'float(0)'

Prefixing a value with a type in parentheses forces the value to be cast into that type, whether it makes sense or not. The advantage in doing so is that it's now obvious what your code does and the resulting behavior becomes easier to understand and more predictable. This will make it easier to spot and fix problems. Let's change our example from before accordingly:

<?php

$i = 42;
$s = "derp";

if ((string)$i < $s) {
    echo "'42' is less than 'derp'";
}

We now convert the integer 42 into the string '42' before comparing it with 'derp'. Instead of comparing 42 to 0, as before, we now compare two strings and as a string comparison, the result actually makes sense: '4' comes before 'd'. So, if you would have used this comparison to sort a list of strings alphanumerically, the result would now be correct.

Guidelines

  • for primitive values, use === to check for equality and !== for inequality
  • use specific comparison functions like strcmp(), if possible.
  • be aware of implicit type casting when using any other comparison operator
  • for objects, === checks, if two values are the same object
    • other operators are not very useful on objects.
    • you should implement specific methods for comparisons between objects.
  • use explicit type casting to make your intent clear in your code, if it's ambiguous
  • consult the manual frequently

Further Reading

Sadly, there are no simple, universal rules for dealing with the behavior of types in PHP. There are just too many exceptions and unexpected cases. That's why it's important to be aware of this problem and refer to the manual when in doubt. The PHP manual has a pretty detailed chapters on type juggling, comparison operators and extensive tables on how values compare to each other. It's good to remember where to find these.