PHP operator== tests by Gynvael Coldwind (2012).
Generated with PHP version 5.4.10.


ARRAY vs ARRAY

Source:
<?php
var_dump
(array() == array()); // equal (obviously)

// Standard arrays.
var_dump(
  array(
NULL,true,2,3.0,"0.4e1","0x5"
  ==
  array(
0,1,2,3,4,5)
); 
// equal (since the normal == operator is used everywhere)

// Deeper.
var_dump(
  array(
NULL,true,array("a"=>"16")) 
  ==
  array(array(),array(
"x","y","z"),array("a"=>"0x10"))
); 
// equal (since the normal == operator is used everywhere)

// Associative.
var_dump(array("10"=>5) == array("0xa"=>5)); // not equal (key strings
                                             // must be exact match)

var_dump(array("5"=>"10") == array("5"=>"0xa")); // equal

Results:
bool(true)
bool(true)
bool(true)
bool(false)
bool(true)

ARRAY vs OBJECT

Source:
<?php
// Array is never equal to an object, unless that object has a 
// cast_object handler supporting to ARRAY casts (I couldn't find
// such a handler, even in SplFixedArray).

// Array vs same array as object?
echo "--- ARRAY vs anonymous object based on that ARRAY\n";
$arr = array("a" => "b");
$obj = (object)$arr;
var_dump($arr == $obj); // not equal

// Sadly (as of PHP 5.4.10) SplFixedArray has no cast_object defined
// for ARRAY, which it totally should have, since it even has a 
// toArray() method.
echo "--- SplFixedArray (no to ARRAY cast_object)\n";
$splarr = new \SplFixedArray(2);
$splarr[0] = 2;
$splarr[1] = "foo";
var_dump($splarr);
var_dump($splarr == array(2"foo")); // not equal ($splarr has no casts)
//var_dump((array)$splarr); // it can be cast
// var_dump((array)$splarr == array(2, "foo")); this would be equal



Results:
--- ARRAY vs anonymous object based on that ARRAY
bool(false)
--- SplFixedArray (no to ARRAY cast_object)
object(SplFixedArray)#2 (2) {
  [0]=>
  int(2)
  [1]=>
  string(3) "foo"
}
bool(false)

ARRAY vs RESOURCE

Source:
<?php
// Never equal.
$f fopen("php://output""w");
var_dump($f);
var_dump(array() == $f);
var_dump(array(1,2,3) == $f);
var_dump(array("a"=>"b") == $f);

echo 
"--- closing resource\n";
fclose($f);
var_dump($f);
var_dump(array() == $f);
var_dump(array(1,2,3) == $f);
var_dump(array("a"=>"b") == $f);

Results:
resource(11) of type (stream)
bool(false)
bool(false)
bool(false)
--- closing resource
resource(11) of type (Unknown)
bool(false)
bool(false)
bool(false)

BOOL vs ARRAY

Source:
<?php
var_dump
(false == array()); // equal (array empty is cast to false)
var_dump(true == array(1,2,3)); // equal (non-empty array is cast to true)
var_dump(true == array(1=>1,2=>2,3=>3)); // equal

Results:
bool(true)
bool(true)
bool(true)

BOOL vs BOOL

Source:
<?php
// Equals:
var_dump(true == true);
var_dump(false == false);

// Not equal:
var_dump(true == false);

Results:
bool(true)
bool(true)
bool(false)

BOOL vs DOUBLE

Source:
<?php
echo("--- equals\n");
var_dump(false == 0.0);   // equal
var_dump(true == 1234.0); // equal
var_dump(true == -INF);   // equal
echo("--- not equals\n");
var_dump(true == 0.0);     // not equal
var_dump(false == 1234.0); // not equal
echo("--- NANs\n");
var_dump(true == NAN);  // equal (non IEEE 754?)
var_dump(false == NAN); // not equal (I would expect this to be equal)

Results:
--- equals
bool(true)
bool(true)
bool(true)
--- not equals
bool(false)
bool(false)
--- NANs
bool(true)
bool(false)

BOOL vs LONG

Source:
<?php
var_dump
(false == 0);    // equal
var_dump(true == 1234);  // equal
var_dump(true == -1234); // equal

var_dump(true == 0);     // not equal
var_dump(false == 1234); // not equal

Results:
bool(true)
bool(true)
bool(true)
bool(false)
bool(false)

BOOL vs OBJECT

Source:
<?php
// Standard PHP objects cast_object always returns bool(TRUE) when
// the object is cast to BOOL.
echo "--- standard PHP objects\n";
class 
Test {}; // A standard class.
$oTest = new Test;
$oStd = (object)array("a"=>"b");
var_dump(true == $oTest); // equal
var_dump(true == $oStd);  // equal

// SimpleXML object's cast_object actually does does a smart BOOL
// conversion
echo "--- SimpleXMLElement (sxe) objects\n";
$sxe_empty = new \SimpleXMLElement("<a></a>");
$sxe_good  = new \SimpleXMLElement("<a><b>xyz</b></a>");

var_dump(true == $sxe_empty); // not equal
var_dump(true == $sxe_good);  // this should be equal, and honestly I have no
                              // idea why this is not equal, especially that
                              // (bool)$sxe_good is true
var_dump(false == $sxe_empty); // equal
var_dump(false == $sxe_good);  // equal (?why? see above note)

// FilesystemIterator's cast_object returns FAILURE for BOOL conversion
echo "--- FilesystemIterator's FAILURE to cast objects\n";
$i = new \FilesystemIterator(dirname(__FILE__));
var_dump(true  == $i); // not equal
var_dump(false == $i); // not equal (sic!)

// These tests do not cover objects with no cast_object handler. However
// in such case it would be:
// var_dump(true == $obj);  // equal
// var_dump(false == $obj); // not equal

Results:
--- standard PHP objects
bool(true)
bool(true)
--- SimpleXMLElement (sxe) objects
bool(false)
bool(false)
bool(true)
bool(true)
--- FilesystemIterator's FAILURE to cast objects
bool(false)
bool(false)

BOOL vs RESOURCE

Source:
<?php
$f 
fopen("php://output""w");

var_dump($f);
var_dump(true == $f);  // equal
var_dump(false == $f); // not equal

// Free up resource.
fclose($f);
var_dump($f);
var_dump(true == $f);  // still equal
var_dump(false == $f); // still not equal

// false == $f is equal only for resource id == 0 (not sure how to get such)

Results:
resource(25) of type (stream)
bool(true)
bool(false)
resource(25) of type (Unknown)
bool(true)
bool(false)

BOOL vs STRING

Source:
<?php
var_dump
(false == "");     // equal \ only these two exact strings are
var_dump(false == "0");    // equal / cast to false
var_dump(false == "00");   // not equal
var_dump(true  == "asdf"); // equal

Results:
bool(true)
bool(true)
bool(false)
bool(true)

DOUBLE vs ARRAY

Source:
<?php
// Never equal.
var_dump(0.0 == array()); // Not equal even in case of empty array.
var_dump(1.0 == array());
var_dump(1.0 == array(1,2,3));
var_dump(3.0 == array(1,2,3));
var_dump(1.0 == array(1=>1,2=>2,3=>3));
var_dump(3.0 == array(1=>1,2=>2,3=>3));

Results:
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)

DOUBLE vs DOUBLE

Source:
<?php
var_dump
(0.0 == 1.0); // not equal

echo "--- out of precission test\n";
var_dump(9223372036854775806.0 == 9223372036854775808.0); // equal (expected)

echo "--- NaN test\n";
var_dump(0.0 == NAN); // not equal
var_dump(NAN == NAN); // not equal (IEEE-754 compliant)
var_dump(INF == NAN); // not equal

Results:
bool(false)
--- out of precission test
bool(true)
--- NaN test
bool(false)
bool(false)
bool(false)

DOUBLE vs OBJECT

Source:
<?php
// Identical to LONG vs OBJECT.

// Standard PHP objects cast_object always returns DOUBLE(1.0) and raises 
// an E_NOTICE (I wonder why it doesn't just return FAILURE).
echo "--- standard PHP objects\n";
class 
Test {}; // A standard class.
$oTest = new Test;
$oStd = (object)array("a"=>"b");
var_dump(1.0 == $oTest); // equal
var_dump(1.0 == $oStd);  // equal

// FilesystemIterator's cast_object returns FAILURE for DOUBLE conversion
echo "--- FilesystemIterator's FAILURE to cast objects\n";
$i = new \FilesystemIterator(dirname(__FILE__));
var_dump(1.0 == $i); // not equal
var_dump(0.0 == $i); // not equal

// These tests do not cover objects with no cast_object handler. However
// in such case it would be:
// var_dump(1.0 == $obj);  // equal (and not equal for any other)

Results:
--- standard PHP objects

Notice: Object of class DOUBLE_vs_OBJECT\Test could not be converted to double in TESTS(27) : eval()'d code on line 11
bool(true)

Notice: Object of class stdClass could not be converted to double in TESTS(27) : eval()'d code on line 12
bool(true)
--- FilesystemIterator's FAILURE to cast objects
bool(false)
bool(false)

DOUBLE vs RESOURCE

Source:
<?php
$f 
fopen("php://output""w");

// Other tests.
var_dump(0.0 == $f); // not equal
var_dump(1.0 == $f); // not equal

// Let's get a textual description of the resource, extract
// the resource id as long, and compare it.
// Note: Alternative way would be to do (int)$f.
echo "--- compare with exact (to integer part) resource id\n";
var_dump($f); // resource(5) of type (stream)
var_dump((string)$f); // "Resource id #5"
list($id) = sscanf((string)$f"Resource id #%d");
$id = (double)$id;

var_dump($id);
var_dump($id == $f); // equal
var_dump(($id+0.1) == $f); // not equal since DOUBLE is cast to LONG
                           // before comparison

echo "--- close, compare again\n";
fclose($f);
var_dump($id == $f); // still equal


Results:
bool(false)
bool(false)
--- compare with exact (to integer part) resource id
resource(37) of type (stream)
string(15) "Resource id #37"
float(37)
bool(true)
bool(false)
--- close, compare again
bool(true)

DOUBLE vs STRING

Source:
<?php
// Some weird non numeric strings.
var_dump(0.0 == "hello there"); // equal
var_dump(0.0 == ""); // equal

// Numeric strings.
echo "--- numeric strings\n";
var_dump(5.0 == "5");                  // equal
var_dump(5.0 == "0.05e2");             // equal (double supported)
var_dump(5.0 == "      \t\n\r  +5");   // equal (whitespace ignored)
var_dump(5.0 == "5and anything else"); // equal (additional chars ignored)
var_dump(5.0 == "0x5");                // equal (hex supported)

echo "--- out of precission test\n";
var_dump(9223372036854775806.0 == "9223372036854775808.0"); // equal (expected)

// Note: is there any case where STRING->DOUBLE cast would give
// different result than a STRING->LONG->DOUBLE cast? That's quite
// possible.

Results:
bool(true)
bool(true)
--- numeric strings
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
--- out of precission test
bool(true)

LONG vs ARRAY

Source:
<?php
// Always not equal.
var_dump(== array()); // Not equal even in case of empty array.
var_dump(== array());
var_dump(== array(1,2,3));
var_dump(== array(1,2,3));
var_dump(== array(1=>1,2=>2,3=>3));
var_dump(== array(1=>1,2=>2,3=>3));

Results:
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)

LONG vs DOUBLE

Source:
<?php
var_dump
(== 1.0); // equal
var_dump(== 2.0); // not equal
var_dump(== INF); // not equal
var_dump(== NAN); // not equal

// 64-bit PHP only (this matters since the conversion boundary is
// in a different place; so on 32-bit PHP the first operand is
// double)
echo "--- long number vs double\n";
if(
is_int(9223372036854775807)) {
  
// All are equal. This is over DOUBLE precission, so this is 
  // expected and correct behavior.
  
var_dump(9223372036854775807 == 9223372036854775806.0);
  
var_dump(9223372036854775807 == 9223372036854775807.0);
  
var_dump(9223372036854775807 == 9223372036854775808.0);
} else {
  echo 
"info: skipping long number tests due to 32-bit PHP\n";
}
Results:
bool(true)
bool(false)
bool(false)
bool(false)
--- long number vs double
info: skipping long number tests due to 32-bit PHP

LONG vs LONG

Source:
<?php
// No magic here.
var_dump(== 1); // equal
var_dump(== 2); // not equal

Results:
bool(true)
bool(false)

LONG vs OBJECT

Source:
<?php
// Standard PHP objects cast_object always returns LONG(1) and raises 
// an E_NOTICE (I wonder why it doesn't just return FAILURE).
echo "--- standard PHP objects\n";
class 
Test {}; // A standard class.
$oTest = new Test;
$oStd = (object)array("a"=>"b");
var_dump(== $oTest); // equal
var_dump(== $oStd);  // equal

// FilesystemIterator's cast_object returns FAILURE for BOOL conversion
echo "--- FilesystemIterator's FAILURE to cast objects\n";
$i = new \FilesystemIterator(dirname(__FILE__));
var_dump(== $i); // not equal
var_dump(== $i); // not equal

// These tests do not cover objects with no cast_object handler. However
// in such case it would be:
// var_dump(1 == $obj);  // equal

Results:
--- standard PHP objects

Notice: Object of class LONG_vs_OBJECT\Test could not be converted to int in TESTS(27) : eval()'d code on line 9
bool(true)

Notice: Object of class stdClass could not be converted to int in TESTS(27) : eval()'d code on line 10
bool(true)
--- FilesystemIterator's FAILURE to cast objects
bool(false)
bool(false)

LONG vs RESOURCE

Source:
<?php
$f 
fopen("php://output""w");

// Other tests.
var_dump(== $f); // not equal
var_dump(== $f); // not equal

// Let's get a textual description of the resource, extract
// the resource id as long, and compare it.
// Note: Alternative way would be to do (int)$f.
echo "--- compare with exact resource id\n";
var_dump($f); // resource(5) of type (stream)
var_dump((string)$f); // "Resource id #5"
list($id) = sscanf((string)$f"Resource id #%d");
var_dump($id);

var_dump($id == $f); // equal

echo "--- close, compare again\n";
fclose($f);
var_dump($id == $f); // still equal

Results:
bool(false)
bool(false)
--- compare with exact resource id
resource(51) of type (stream)
string(15) "Resource id #51"
int(51)
bool(true)
--- close, compare again
bool(true)

LONG vs STRING

Source:
<?php
echo "--- long vs strange strings\n";
var_dump(== "any nonparsable string is equal to zero"); // equal
var_dump(== "5");                  // equal
var_dump(== "0.05e2");             // equal (double supported)
var_dump(== "      \t\n\r  +5");   // equal (whitespace ignored)
var_dump(== "5and anything else"); // equal (additional chars ignored)
var_dump(== "0x5");                // equal (hex supported)

// 64-bit PHP only (this matters since the conversion boundary is
// in a different place; so on 32-bit PHP the first operand is
// double)
echo "--- long number vs long number in a string\n";
if(
is_int(9223372036854775807)) {
  
var_dump(9223372036854775807 == "9223372036854775806"); // not eq (== LONG)
  
var_dump(9223372036854775807 == "9223372036854775807"); // eq     (== LONG)
  
var_dump(9223372036854775807 == "9223372036854775808"); // eq     (== DOUBLE)
} else {
  echo 
"info: skipping long number tests due to 32-bit PHP\n";
}

Results:
--- long vs strange strings
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
--- long number vs long number in a string
info: skipping long number tests due to 32-bit PHP

NULL vs ARRAY

Source:
<?php
var_dump
(NULL == array()); // equal (only if array empty)
var_dump(NULL == array(1,2,3)); // not equal
var_dump(NULL == array(1=>1,2=>2,3=>3)); // not equal

Results:
bool(true)
bool(false)
bool(false)

NULL vs BOOL

Source:
<?php
var_dump
(NULL == true);  // not equal
var_dump(NULL == false); // equal

Results:
bool(false)
bool(true)

NULL vs DOUBLE

Source:
<?php
var_dump
(NULL == 0.0); // equal
var_dump(NULL == NAN); // not equal (hmm non IEEE754 NAN?)
var_dump(NULL == INF); // not equal
var_dump(NULL == -INF); // not equal

Results:
bool(true)
bool(false)
bool(false)
bool(false)

NULL vs LONG

Source:
<?php
var_dump
(NULL == -1); // not equal
var_dump(NULL == 0);  // equal
var_dump(NULL == 1);  // not equal

Results:
bool(false)
bool(true)
bool(false)

NULL vs NULL

Source:
<?php var_dump(NULL == NULL); // always equal

Results:
bool(true)

NULL vs OBJECT

Source:
<?php
date_default_timezone_set
("Europe/Zurich");

class 
Test {}; // A standard class.
$oTest = new Test;
$oStdEmpty = (object)array();
$oStd = (object)array("a"=>"b");
$oDate = new \DateTime// php_date builtin class.

var_dump(NULL == $oTest); // not equal
var_dump(NULL == $oStdEmpty); // not equal
var_dump(NULL == $oStd); // not equal
var_dump(NULL == $oDate); // not equal
// Should never be equal.

Results:
bool(false)
bool(false)
bool(false)
bool(false)

NULL vs RESOURCE

Source:
<?php
$f 
fopen("php://output""w");

var_dump($f);
var_dump(NULL == $f); // not equal

// Free up resource.
fclose($f);
var_dump($f);
var_dump(NULL == $f); // not equal

// Equal only for resource id == 0 (not sure how to get such)

Results:
resource(68) of type (stream)
bool(false)
resource(68) of type (Unknown)
bool(false)

NULL vs STRING

Source:
<?php
var_dump
(NULL == ""); // equal (only in this case)
var_dump(NULL == "0"); // not equal
var_dump(NULL == "00"); // not equal
var_dump(NULL == "anything");  // not equal

Results:
bool(true)
bool(false)
bool(false)
bool(false)

OBJECT vs OBJECT

Source:
<?php
echo "--- standard object comparator (zend_std_compare_objects)\n";
class 
Test {
  public 
$x 5;
}

$anonobj1 = (object)array("x" => 5);
$anonobj2 = (object)array("x" => " 5 <-- five");
$testobj1 = new Test;
$testobj2 = new Test;

var_dump($testobj1 == $anonobj1); // not equal (different class)
var_dump($anonobj1 == $anonobj2); // equal, all anonymous objects are of the same class

echo "--- deep recursion comparison\n";
echo 
"skipping, since it would ".
  
"<a href=\"https://bugs.php.net/bug.php?id=63882\">crash the PHP "
  
"interpreter</a>\n";
// $testobj1->x = $testobj2;
// $testobj2->x = $testobj1;
// var_dump($testobj1 == $testobj2);
// Skipping, this would crash the interpreter (see bug #63882)

// Zend closures are object and they have a compare handler that says they
// are equal only if it's actually the same object.
echo "--- closure comparison\n";
$closure1 = function(){};
$closure2 = function(){};
var_dump($closure1 == $closure2); // not equal, different objects

$ptr = &$closure1;
var_dump($ptr == $closure1); // equal, same object

// Two objects of different class
echo "--- different object comparison\n";
date_default_timezone_set("Europe/Zurich");
class 
{}

$a = new A;
$b = new \DateTime;

var_dump($a == $b); // equal in PHP 5.4.6 + raises 2x E_NOTICE
                    // not equal in PHP 5.4.10

// Note: I totally ignored proxy objects (since I don't undersand them at
// the moment of creating these tests; not sure where are they used etc;
// actually the code for them exists, but hard to find a reference; TODO)

Results:
--- standard object comparator (zend_std_compare_objects)
bool(false)
bool(true)
--- deep recursion comparison
skipping, since it would crash the PHP interpreter
--- closure comparison
bool(false)
bool(true)
--- different object comparison
bool(false)

OBJECT vs RESOURCE

Source:
<?php
$f 
fopen('php://output''w');
var_dump($f);

// Standard PHP objects cast_object will always fail (so it's always not equal).
$obj = (object)array("a"=>"b");
var_dump($obj == $f); // not equal

fclose($f); // Changes nothing.
var_dump($obj == $f); // not equal

// Note: I don't know of any built-in / standard extension class that has the
// cast_object handler supporting to RESOURCE conversion.

// PDORow using sqlite (has no cast_object, so it will be cast to LONG(1),
// and will be compared with the resource cast to LONG).
echo "--- PDORow object (has no cast_object handler)\n";
try {
  
// Create a PDORow object using sqlite.
  
$r = new \PDO("sqlite::memory:");
  
$r $r->query("SELECT sqlite_version()");
  
$r $r->fetch(\PDO::FETCH_LAZY);

  
var_dump($r == $f); // This would be equal if this would be resource #1.
                      // Note: this will raise an E_NOTICE.

} catch (Exception $e) {
  echo 
"skipping PDORow test ({$e->message})\n";
}

Results:
resource(75) of type (stream)
bool(false)
bool(false)
--- PDORow object (has no cast_object handler)

Notice: Object of class PDORow could not be converted to int in TESTS(27) : eval()'d code on line 25
bool(false)

RESOURCE vs RESOURCE

Source:
<?php
$f 
fopen("php://output""w");
$g fopen("php://input""r");

var_dump($f == $f); // equal (obviously)
var_dump($f == $g); // not equal

Results:
bool(true)
bool(false)

STRING vs ARRAY

Source:
<?php
// Never equal.
var_dump("" == array());
var_dump("" == array(1,2,3));
var_dump("1" == array(1,2,3));
var_dump("1,2,3" == array(1,2,3));

Results:
bool(false)
bool(false)
bool(false)
bool(false)

STRING vs OBJECT

Source:
<?php
// Anonymous object with no __tostring method (not equal to anything).
echo "--- anonymous object without __tostring\n";
$obj = (object)array("a"=>"b");
var_dump($obj == "");     // not equal
var_dump($obj == "0");    // not equal
var_dump($obj == "1");    // not equal
var_dump($obj == "asdf"); // not equal

// Custom objects with __tostring method.
echo "--- custom objects with __tostring method\n";
class 
Test {
  function 
__tostring() {
    return 
"5";
  }
}

$test = new Test;
var_dump($test == "5");      // equal
var_dump($test == "+0.5e1"); // equal

echo "--- custom objects with __tostring method returning non-string\n";
set_error_handler(function($errno,$errstr){return true;});

class 
TestNonString {
  function 
__tostring() {
    return 
$this// in case this returns non-string, it will 
                  // throw an error, but will return an
                  // empty string ("")
  
}
}

var_dump(new TestNonString == ""); // bool(equal)

// This code would crash the PHP interpreter (stack exhaustion):
echo "--- recursive == operator in __tostring\n";
echo 
"skipping since it would ".
     
"<a href=\"https://bugs.php.net/bug.php?id=46754\">crash the interpreter</a>\n";
//class Xyz {
//  function __tostring() {
//    //return $this->__tostring(); // -- this would not crash
//     return $this == "crash me"; // -- stack overflow crash
//   }
// }
// 
// new Xyx == "plz die";
//
// known: https://bugs.php.net/bug.php?id=46754

// PDORow using sqlite (has no cast_object, so it will be cast to LONG(1),
// so will be the string).
echo "--- PDORow object (has no cast_object handler)\n";
try {

  
// Create a PDORow object using sqlite.
  
$r = new \PDO("sqlite::memory:");
  
$r $r->query("SELECT sqlite_version()");
  
$r $r->fetch(\PDO::FETCH_LAZY);

  
var_dump($r == "0");        // not equal
  
var_dump($r == "");         // not equal
  
var_dump($r == "anything"); // not equal

  
var_dump($r == "1");               // equal
  
var_dump($r == "  \t\r\n+0.01e2"); // equal

} catch (Exception $e) {
  echo 
"skipping PDORow test ({$e->message})\n";
}

// Note: This test does not cover a custom built-in cast_object supporting
// to STRING coversion. Some classess like Tidy do support it, but in the end
// the behavior is identical to that of the stdObjects (i.e. it returns a string
// if it's able to).

Results:
--- anonymous object without __tostring
bool(false)
bool(false)
bool(false)
bool(false)
--- custom objects with __tostring method
bool(true)
bool(true)
--- custom objects with __tostring method returning non-string
bool(true)
--- recursive == operator in __tostring
skipping since it would crash the interpreter
--- PDORow object (has no cast_object handler)
bool(false)
bool(false)
bool(false)
bool(true)
bool(true)

STRING vs RESOURCE

Source:
<?php
$f 
fopen("php://output""w");

// Other tests.
var_dump("" == $f);     // not equal
var_dump("asdf" == $f); // not equal

// Let's get a textual description of the resource, extract
// the resource id as long, and compare it.
// Note: Alternative way would be to do (int)$f.
echo "--- compare with exact resource id\n";
var_dump($f); // resource(5) of type (stream)
var_dump((string)$f); // "Resource id #5"
list($id) = sscanf((string)$f"Resource id #%s");
var_dump($id);

var_dump($id == $f); // equal
var_dump("      +{$id}e0" == $f); // equal

echo "--- close, compare again\n";
fclose($f);
var_dump($id == $f); // equal
var_dump("      +{$id}e0" == $f); // equal

Results:
bool(false)
bool(false)
--- compare with exact resource id
resource(86) of type (stream)
string(15) "Resource id #86"
string(2) "86"
bool(true)
bool(true)
--- close, compare again
bool(true)
bool(true)

STRING vs STRING

Source:
<?php
// STRING vs STRING is one strange thing. Don't use it unless you know
// EXACTLY what you're doing.

echo "-- equals\n";
var_dump("asdf" == "asdf"); // obviously equal
var_dump("1.00000000000000001" == "0.1e1"); // equal
var_dump("+1" == "0.1e1");      // equal
var_dump("1e0" == "0.1e1");     // equal
var_dump("-0e10" == "0");       // equal
var_dump("1000" == "0x3e8");    // equal
var_dump("1234" == "    1234"); // equal

echo "-- non equals\n";
var_dump("1xyz" == "0.1e1");    // not equal
var_dump("1xyz" == "0.1e1xyz"); // not equal
var_dump("asdf" == "ASDF");     // not equal
var_dump("asdf" == "    asdf"); // not equal
var_dump("0"    == "");         // not equal

// Out of double precision comparison:
echo "-- double out of precission\n";
var_dump("1234512345123451234512345" == "1234512345123451234512346");
// equal for 32-bit PHP
// not equal for 64-bit PHP

Results:
-- equals
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
-- non equals
bool(false)
bool(false)
bool(false)
bool(false)
bool(false)
-- double out of precission
bool(false)