Баги PHP

PHP как и любой другой язык содержит целый ряд багов и особенностей.
Вот некоторые из них.

Приоритет операторов

Вы должно быть думаете, что операторы в PHP работают как в C. К сожалению существуют тонкие различия. К примеру следующие куски кода на C и PHP выведут различные результаты:

<?php
echo 1 ? 2 : 3 ? 4 : 5;

//Вывод: 4

 

#include <stdio.h>
int main() {
printf("%d\n", 1 ? 2 : 3 ? 4 : 5);
return 0;

//Вывод: 2
}

Нельзя выполнить неопределенный конструктор

Может привести к ошибкам по мере правок кода.

<?php

class A {
}

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

new B();
//Вывод: PHP Fatal error:  Cannot call constructor in

Переопределение приватных методов

<?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();
//Вывод: foo in B
// foo in A

Конструктор это просто еще один метод

Конструктор и деструктор ведут себя как обычные методы и не вызывают выделение или освобождение памяти.

<?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";
}
//Вывод:
//memory:237632
//memory:237632
//memory:237632
//memory:237632
//...

Нельзя доверять <

Операторы: < и > могут приводить к странным вещам, когда есть несоответствие типов.

<?php

echo (int)(null > -1), "\n";
echo (int)(null < 1), "\n";
echo (int)(null == 0), "\n";
//Вывод: 0
//1
//1

Косяк с инкриментом:

<?php

$x = "x";
$x++; $x++; $x++;
echo $x, "\n";
$x--; $x--; $x--;
echo $x, "\n";
//Вывод: aa
//aa

А вот еще один непонятный баг php:

        <?php

        $x = null;
        var_dump(--$x);
        var_dump(++$x);

//Вывод: NULL
//int(1)

Когда вы комбинируете с оператором «<» вы можете получить забавные вещи:

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

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

$x++;
echo '$x++ = ', $x, "\n";
echo $x, ' < yy = ';
echo (int)($x < 'yy'), "\n";
//Вывод:
//y < yy = 1
//$x++ = z
//z < yy = 0
//$x++ = aa
//aa < yy = 1

Нельзя доверять методу get_class:

Примечание: такое поведение задокументировано в руководстве. Зачем кому-то потребовалось именно такое поведение?

<?php

class A { }

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

Foo::bar(new A());
Foo::bar(null);
//Вывод:
//A
//Foo

Кривое преобразование типов

<?php

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

function foo(string $s) {
echo $s.' is a string';
}
foo(new Stringy());
//Вывод:
//PHP Catchable fatal error:  Argument 1 passed to foo() 
//must be an instance of string, instance of Stringy given, called 
//in ... on line 12 and defined in ... on line 9

А вот еще:

<?php

function foo(string $s){}

foo("hello world");

//Вывод:
//Catchable fatal error: Argument 1 passed to foo() 
//must be an instance of string,
//string given, called in ...

Считать или не считать

Метод сборки мусора подсчетом ссылок имеет некоторые преимущества и не достатки. Преимущества в том что этот метод доволько просто реализовать, а также скорость сборки мусора при таком методе будет достаточно высокая. Минусы обычно можно не принимать во внимание в контексте веб-приложения.
Не разбираясь в концепции подсчета ссылок, программист может наткнуться на очередной странный баг php. Пример:

<?php

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

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

// a - содержит массив с элементом (ссылкой на i)
$a = foo();
$b = $a;
$a[0] = 2;
echo $b[0], "\n";

$a = bar();
$b = $a;
$a[0] = 2;
echo $b[0], "\n";
//Вывод:
//1 2

Не достаточно «protected»

Для примера в Java вызов бы не прошел (в том случае если классы в разных пакетах).
<?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();
//Вывод:
// protected method

Трудно предсказать итераторы

Непонятно какой итератор использует php (внутренний или внешний). В некоторых случаях, foreach использует внутренний итератор массива, в других случаях он использует внешний итератор. Это может привести к путанице в коде.

<?php
$x = range(4, 10);
print_r($x);
echo "\n";

foreach ($x as $v) {
echo key($x),",", current($x), " ";
}
echo "<br/>";

$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";
//ВЫВОД:
//1,5 1,5 1,5 1,5 1,5 1,5 1,5
//0,4 0,4 0,4 0,4 0,4 0,4 0,4
//1,5 2,6 3,7 4,8 5,9 6,10 ,

Не транзитивное сравнение

PHP’s sort документация предупреждает, чтобы не сортировали элементы массива с разным типом данных. Вот что будет если так сделать:
<?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";
//Вывод:
//0 1

in_array не нашел элемент в массиве

Скорее особенность работы функции, чтобы ее избежать надо использовать strict mode:
<?php
$arr = array('1', true, '23');
var_dump(in_array('22', $arr));
//Вывод: true
var_dump(in_array('22', $arr, true)
//Вывод: false
Тестировалось на php версии 5.4