Alok Menghrajani
Previously: security engineer at Square, co-author of HackLang, put the 's' in https at Facebook. Maker of CTFs.
This blog does not use any tracking cookies and does not serve any ads. Enjoy your anonymity; I have no idea who you are, where you came from, and where you are headed to. Let's dream of an Internet from times past.
Home | Contact me | Github | RSS feed | Consulting services | Tools & games
PHP turtles
Turtles all the way down.
- operator precedence
- calling instance methods in static context
- can't call future constructor
- redefine private methods
- __construct is just another method
- curl_multi_exec
- can't trust ==
- can't trust <
- funny increments
- can't trust get_class()
- constructor does not get called, but destructor sure does
- inflexible type hints
- to count or not to count
- error messages
- protected, not really
- hard to predict iterators
- non-transitive comparison
- setting CURLOPT_SSL_VERIFYHOST to true disables certificate validation
- more stuff...
turtle 1: operator precedence
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)
turtle 2: calling instance methods in static context
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)
turtle 3: can't call future constructor
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)
turtle 4: redefine private methods
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)
turtle 5: __construct is just another method
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)
turtle 6: curl_multi_exec
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)
turtle 7: can't trust ==
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)
turtle 8: can't trust <
< 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)
turtle 9: funny increments
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)
turtle 10: can't trust get_class()
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)
turtle 11: constructor does not get called, but destructor sure does
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)
turtle 12: inflexible type hints
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)
turtle 13: to count or not to count
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)
turtle 14: error messages
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)
turtle 15: protected, not really
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)
turtle 16: hard to predict iterators
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)
turtle 17: non-transitive comparison
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)
turtle 18: setting CURLOPT_SSL_VERIFYHOST to true disables certificate validation
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)