Turtles all the way down
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 echo 1 ? 2 : 3 ? 4 : 5;(click to see output)
#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
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
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
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
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
$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
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 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 $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
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
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
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
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
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)
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 ::(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
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
$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)