PHP turtles

Turtles all the way down.

  1. operator precedence
  2. calling instance methods in static context
  3. can't call future constructor
  4. redefine private methods
  5. __construct is just another method
  6. curl_multi_exec
  7. can't trust ==
  8. can't trust <
  9. funny increments
  10. can't trust get_class()
  11. constructor does not get called, but destructor sure does
  12. inflexible type hints
  13. to count or not to count
  14. error messages
  15. protected, not really
  16. hard to predict iterators
  17. non-transitive comparison
  18. setting CURLOPT_SSL_VERIFYHOST to true disables certificate validation
  19. more stuff...

You would think that PHP's operators are based on C's. Unfortunately there are subtle differences. I usually end up abusing '(' and ')' in my expressions. For example, the following pieces of similar PHP and C code don't output the same result!

PHP code

<?php

echo 1 ? 2 : 3 ? 4 : 5;
(click to see output)

C code

#include <stdio.h>

int main() {
  printf("f=%d\n", 1 ? 2 : 3 ? 4 : 5);
  return 0;
}
(click to see output)

Not a huge deal, since this can easily be solved with a linter.

PHP code

<?php

class A {
  public function printName() {
   echo 'I am:', get_class($this), "\n";
  }

  public function foo() {
    return A::printName();
  }
}

class B {
  public function foo() {
    A::printName();
  }
}

$a = new A();
$a->foo();

$b = new B();
$b->foo();
(click to see output)

Can lead to bugs as code changes over time.

PHP code

<?php

class A {
}

class B extends A {
  public function __construct() {
    // make sure parent constructor gets called if someone adds one
    parent::__construct();
  }
}

new B();
(click to see output)

It's debatable what's the right thing to do. PHP's design decision isn't unreasonable in this case.

PHP code

<?php

class A {
  private function foo() {
    return 'foo in A';
  }

  public function bar() {
    echo $this->foo(), "\n";
  }
}

class B extends A {
  private function foo() {
    return 'foo in B';
  }

  public function bar2() {
    echo $this->foo(), "\n";
  }
}

$b = new B();
$b->bar2();
$b->bar();
(click to see output)

Unless you are writing at the compiler/framework layer, calling __construct and __destruct behaves like just any methods and does not cause you to allocate or free any memory.

PHP code

<?php

class A {
  public $v;

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

$a = new A(42);
$b = range(0, 9);
for ($i=0; $i<10; $i++) {
  $b[$i] = $a->__construct($i);
  echo 'memory:', memory_get_usage(), "\n";
}
(click to see output)

curl_multi_exec does not affect curl_errno(), but it does affect the return value of curl_error().

PHP code

<?php

$c = curl_init('http://www.google.com:443/');
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
$mh = curl_multi_init();
curl_multi_add_handle($mh,$c);
$active = null;
do {
  $mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM || $active);

$r = curl_multi_info_read($mh);
echo $r['result'], "\n";
echo curl_errno($c), "\n";
echo curl_error($c), "\n";
(click to see output)

You can't trust ==, even when you know the types are going to match.

credits: erling

PHP code

<?php

echo (int)("  4"  == "    4"), "\n";
echo (int)("  4 " == "    4 "), "\n";
(click to see output)

< and > can do weird things when there's a type mismatch.

PHP code

<?php

echo (int)(null > -1), "\n";
echo (int)(null < 1), "\n";
echo (int)(null == 0), "\n";
(click to see output)

In general, I have tried to avoid critizing PHP's API, however this one deserves to be listed.

PHP code

<?php

$x = "x";
$x++; $x++; $x++;
echo $x, "\n";
$x--; $x--; $x--;
echo $x, "\n";
(click to see output)

And another weird case:

<?php

$x = null;
var_dump(--$x);
var_dump(++$x);
(click to see output)

When you combine with <, you end up with fun things:

<?php

$x = 'y';
echo (int)($x < 'yy'), "\n";

$x++;
echo (int)($x < 'yy'), "\n";

$x++;
echo (int)($x < 'yy'), "\n";
(click to see output)

Note: this behavior is documented in the manual. Why would anyone go out of their way to implement this behavior?

credits: jfrank

PHP code

<?php

class A { }

class Foo {
  public static function bar($x) {
    echo get_class($x), "\n";
  }
}

Foo::bar(new A());
Foo::bar(null);
(click to see output)

This caused us hours in debugging time. Things were eventually fixed in php 5.3.x.

credits: pgriess

PHP code

<?php

class Foo {
  public function __construct($a) {
    echo 'Foo::__construct()',"\n";
  }

  public function __destruct() {
    echo 'Foo::__destruct()',"\n";
  }
}

function blah() {
  throw new Exception();
}

try {
  $f = new Foo(blah());
} catch (Exception $e){
}
(click to see output)

Note: Removed, since this only applies to hphp.

PHP code

<?php

class Stringy {
  public function __toString() {
    return "I am Stringy";
  }
}

function foo(string $s) {
  echo $s.' is a string';
}
foo(new Stringy());
(click to see output)

Reference counting based garbage collectors have lots of pros and some cons. The pros are that it's usually simpler to implement and runs faster. The cons can usually be worked around & ignored in the context of web applications.

In PHP, the concept of references is exposed to the programmer, which can lead to various nasty bugs, as shown by the following example.

PHP code

<?php

function foo() {
  $i = 1;
  return array(&$i);
}

function bar() {
  $i = 1;
  return array(&$i, &$i);
}

$a = foo();
$b = $a;
$a[0] = 2;
echo $b[0], "\n";

$a = bar();
$b = $a;
$a[0] = 2;
echo $b[0], "\n";
(click to see output)

Similarly, the following code's output is hard to predict:

credits: julienv

PHP code

<?php

function foo($x) {
  $x[0] = 42;
}

$a = array(1);
$b = array(&$a[0]);
foo($b);

$c = array(1);
$d = array(&$c[0]);
$c=0;
foo($d);

echo $b[0], "\n";
echo $d[0], "\n";
(click to see output)

And another case where the fact that $a is pointed to leaks:

PHP code

<?php

$a = array(); $b = array();
$c = &$b;
$a[] = 1; $b[] = 1;
unset($a[0]); unset($b[0]);
$c = $a; $c = $b;
$a[] = 2; $b[] = 2;
print_r($a); print_r($b);
(click to see output)

You can accidentally run into this when using foreach loops:

PHP code

<?php

$a = array(1); $b = array(1);

foreach ($a as $value) {}
foreach ($b as &$value) {}
$x = $a; $y = $b;
$x[0] = 'a'; $y[0] = 'a';
print_r($a); print_r($b);
(click to see output)

The error messages you get sometimes don't make sense. It's however not a big issues, since Googling around will quickly help you figure out what's going on.

PHP code

<?php

::
(click to see output)

<?php

function foo(string $s){}

foo("hello world");
(click to see output)

<?php
$x =
?>
(click to see output)

PHP's handling of the protected visibiltiy on method is counter intuitive in many ways. Here is one example.

PHP code

<?php

class A {
  public static function f1() {
    $b = new B();
    $b->f2();
  }
}

class B extends A {
  protected function f2() {
    echo "protected method\n";
  }
}

A::f1();
(click to see output)

PHP has two ways to deal with array iterators. In some cases, foreach uses an array's internal iterator, in other cases it uses an external iterator. This can lead to confusing code.

PHP code

<?php

$x = range(4, 10);
foreach ($x as $v) {
  echo key($x),",", current($x), " ";
}
echo "\n";

$x = range(4, 10);
$y = $x;
foreach ($x as $v) {
  echo key($x),",", current($x), " ";
}
echo "\n";

$x = range(4, 10);
$y = &$x;
foreach ($x as $v) {
  echo key($x),",", current($x), " ";
}
echo "\n";
(click to see output)

PHP's sort documentation warns about sorting arrays with mixed types. Most dynamically typed languages have some sort of similar quirk. Here is an overview of PHP's:

PHP code

<?php

function is_sorted($a) {
  $t = $a;
  sort($a);
  return $t === $a;
}

function is_really_sorted($a) {
  $t = $a;
  sort($a);
  sort($a);
  return $t === $a;
}

$a = array('a', 0);
sort($a);
echo (int)is_sorted($a), "\n";
echo (int)is_really_sorted($a), "\n";
(click to see output)

Another, simpler, example:

PHP code

<?php

$a  = array('a', 0, 'a', 0);
sort($a);
print_r($a);
(click to see output)

Curl's CURLOPT_SSL_VERIFYHOST takes an integer, with 1 being a partial check.

This was fixed in cURL 7.28.1. Setting the option to 1 is equivalent to setting it to 2.

PHP code

<?php

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://alok.quaxio.com/");
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
$r = curl_exec($ch);
if (curl_errno($ch)) {
	echo curl_error($ch);
}
print_r($r);
(click to see output)