PHP мины в целом

Какие сюрпризы нашли другие люди при написании PHP веб-приложений? Существует хорошо известная и подлежащая исправлению проблема с наследованием классов во время компиляции, но я знаю пару других и хотел попробовать составить список лучших особенностей языка.

Замечания:

Я занимал несколько должностей как старший разработчик PHP5, так что работа PHP оплачивает мои счета, этот вопрос не предназначен для того, чтобы разрушить PHP как язык, поскольку каждый отдельный язык, с которым я работал, имеет некоторые хорошо известные или не очень известные сюрпризы,

24 ответа

Я не уверен, что это важно, но необходимость компилировать PHP-скрипты - это огромная проблема с производительностью. В любом серьезном PHP-проекте вам нужен какой-то кеш компилятора, такой как APC, http://eaccelerator.net/, PHP Accelerator или (коммерческая) Zend Platform.

Рекурсивные ссылки утечки памяти

Если вы создаете два объекта и сохраняете их в свойствах друг друга, сборщик мусора никогда не коснется их:

$a = new stdClass;
$b = new stdClass;
$a->b = $b;
$b->a = $a;

Это на самом деле довольно легко сделать, когда большой класс создает маленький вспомогательный объект, который обычно хранит основной класс:

// GC will never clean up any instance of Big.
class Big {
  function __construct() {
    $this->helper = new LittleHelper($this);
  }
}
class LittleHelper {
  function __construct(Big $big) {
    $this->big = $big;
  }
}

Пока PHP предназначен для коротких быстрых запросов страниц, они вряд ли решат эту проблему. Это означает, что PHP не может зависеть от демонов или других приложений, имеющих длительный срок службы.

require_once и include_once часто могут привести к значительному снижению производительности при чрезмерном использовании. Если вы включаете / требуете файл, который содержит класс... подобный шаблон может сэкономить некоторое серьезное время обработки.

class_exists("myFoo") or require("myFoo.someClass.php");

Обновление: это все еще проблема - http://www.techyouruniverse.com/software/php-performance-tip-require-versus-require_once

Обновление: Прочитайте выбранный ответ на следующий вопрос: снизится ли производительность при автозагрузке в php и поиске файла класса? Если реализовано в соответствии с этим, вы в значительной степени минимизируете, насколько это возможно, штрафы за файл include/require.

Веселая мина: глобальные переменные могут влиять $_SESSION когда register_globals включен Но я думаю, это то, что происходит, когда register_globals, сама мина, включена.

NULL и строка "0" являются чистым злом в Php

if ("0" == false) //true
if ("0" == NULL)  //true
if ("0" == "NULL")//true
  • foreach() автоматически копирует массив в фоновом режиме и выполняет итерацию по этой копии. Если у вас большой массив, это снизит производительность. В этих случаях параметры ссылки по ссылке для foreach() являются новыми для php5 или используют цикл for ().

  • Помните о равенстве (==) и идентичности (===).

  • Помните, что представляет собой empty (), а что isset ().


Больше мин сейчас, когда у меня есть немного времени:

  • Не сравнивайте поплавки на равенство. PHP не Matlab, и он просто не предназначен для точной арифметики с плавающей точкой. Попробуй это:
if (0.1 + 0.2 == 0.3)
  echo "equal";
else
  echo "nope"; // <-- ding ding
  • Точно так же не забывайте свои восьмеричные! Int w/ ведущий ноль приводится как восьмеричное.
if (0111 == 111)
  echo "equal";
else
  echo "nope"; // <-- ding ding

Это было отчасти очевидно после факта, но хорошо известная ошибка связана с областью действия и ссылками при использовании в foreach.

foreach($myArray as &$element){
   //do something to the element here... maybe trim or something more complicated
}
//Multiple lines or immediately after the loop

$element = $foobar;

Последняя ячейка в вашем массиве теперь стала $foobar, потому что ссылка в foreach выше все еще находится в текущей области контекста.

Незнание приоритета оператора может вызвать некоторые проблемы:

if ($foo = getSomeValue() && $bar) {
    // …
}
// equals
if ($foo = (getSomeValue() && $bar)) {
    // …
}

@ ошибки глушителя всегда следует избегать.

Пример:

// Don't let the user see an error if this unimportant header file is missing:
@include 'header.inc.php';

С кодом выше, вы никогда не будете знать о каких-либо ошибок в любом коде в header.inc.phpили любая из функций, вызываемых из header.inc.php, и если где-то есть фатальная ошибка, ваша веб-страница остановится, и вы не сможете узнать, что это за ошибка.

__autoload() недавно стал для меня главной миной. Некоторые из наших устаревших кодов и библиотек используют class_exists()и пытается загрузить классы, которые никогда не предназначались для такой загрузки. Много фатальных ошибок и предупреждений. class_exists() все еще может использоваться, если у вас есть автозагрузка, но второй параметр (новый, начиная с PHP 5.2.0) должен быть установлен в false

Числовые строки автоматически преобразуются в целые числа

Это, безусловно, самый уродливый и самый неясный взлом в PHP. Всякий раз, когда у вас есть строка, состоящая из всех цифр, она автоматически обрабатывается, как если бы она была целочисленной в некоторых случаях.

php > var_dump("0" == "00");
bool(true)

Это может стать очень неприятным в сочетании с "ассоциативными массивами" PHP, что приводит к странностям, когда $a == $b не означает, что $arr[$a] == $arr[$b];

php > var_dump(array('00'=>'str(zerozero)', '0'=>'str(zero)'));
array(2) {
  ["00"]=>
  string(13) "str(zerozero)"
  [0]=>
  string(9) "str(zero)"
}

Большая ошибка, которую я видел, что люди становятся жертвами, это точность (в php и других языках).

Если вы хотите немного повеселиться, сравните любое число с плавающей точкой с>> и выясните, сколько раз вы получили ожидаемый результат.

Это привело к падению многих людей, работающих с деньгами внутри PHP и пытающихся принимать логические решения на основе сравнений, которые не позволяют округлять до целого числа.

Например - ткань

Ткань продается в единицах 1 ярд или 1 пол ярда, а также с учетом запасов точных размеров, оставшихся от ткани.

Если эта система не выражена в целых числах, а вместо этого выражена в плавающих точках, это сделает невероятно трудным принятие твердых решений.

Лучше всего выразить 1 половину ярда как 1, например, если у вас есть 300 ярдов ткани, у вас будет инвентарь 600 (600 единиц ярда).

В любом случае, вот что я понял - время на рефакторинг 4 месяцев программирования из-за непонимания точности....

Мой любимый PHP поймал:

Считайте, что это включает в себя:

# ... lots of code ...
$i = 42;
# ... more code ...

Затем используйте это, где-то включить:

for($i = 0; $i < 10; $i++){
    # ...
    include 'that_other_file.php';
}

Затем попытайтесь угадать, сколько раз цикл выполняется. Да, однажды. Лексическая область видимости (и правильная динамическая область видимости) являются решаемыми проблемами. Но не в PHP.

Если вы привыкли к языкам с интеллектуальными логическими операторами, вы попытаетесь сделать что-то вроде:

$iShouldTalkTo = $thisObj || $thatObj;

В PHP $iShouldTalkTo теперь логическое значение. Вы вынуждены написать:

$iShouldTalkTo = $thisObj ? $thisObj : $thatObj;

Из всех примеров того, как ранние дизайнерские решения в PHP пытались удержать руки некомпетентных программистов в обмен на вмешательство компетентных, это может быть тем, что меня раздражает больше всего.

Глубокое повреждение мозга в switch() конструкция изобилует. Учти это:

switch($someVal) {
case true  :
    doSomething();
    break;
case 20    :
    doSomethingElse();
    break;
}

Получается что doSomethingElse() никогда не будет вызван, потому что 'case true' поглотит все истинные случаи $someVal.

Думаю, это оправданно, возможно? Ну, попробуйте это:

for($ix = 0; $ix < 10; $ix++) {
    switch($ix) {
    case 3  :
        continue;
    default :
        echo ':';
    }
    echo $ix;
}

Угадайте, какой у него выход? Должно быть:0:1:2:4:5:6:7:8:9, верно? Нет, это:0:1:23:4:5:6:7:8:9. То есть он игнорирует семантику continue заявление и рассматривает его как break,

Одной из худших является концепция "ассоциативных массивов" PHP, которые являются полностью неудачным гибридом массива, словаря и списка. Авторы PHP, похоже, не уверены, как он должен вести себя в каждом конкретном случае, что приводит к странностям, таким как поведение операторов массивов плюс и array_merge функция.

php > $a = array(1=>'one');
php > $b = array(2=>'two');
php > var_dump($a+$b); /* plus preserves original keys */
array(2) {
  [1]=>
  string(3) "one"
  [2]=>
  string(3) "two"
}
php > var_dump(array_merge($a,$b)); /* array_merge reindexes numeric keys */
array(2) {
  [0]=>
  string(3) "one"
  [1]=>
  string(3) "two"
}


php > $a = array(1=>'one');
php > $b = array(1=>'another one');
php > var_dump($a+$b);  /* plus ignores duplicate keys, keeping the first value */
array(1) {
  [1]=>
  string(3) "one"
}
php > var_dump(array_merge($a,$b)); /* array_merge just adds them all, reindexing */
array(2) {
  [0]=>
  string(3) "one"
  [1]=>
  string(11) "another one"
}

php > $a = array(1,2,3);
php > $b = array(4,5,6);
/* non-associative arrays are really associative arrays with numeric keys… */
php > var_dump($a+$b);  /* … so plus doesn’t work as you’d normally expect */
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
php > var_dump(array_merge($a,$b));  /* you should use array_merge instead */
array(6) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
  [3]=>
  int(4)
  [4]=>
  int(5)
  [5]=>
  int(6)
}

Общая память при работе PHP. Многие крупные проекты просто включают все файлы классов и используют их, когда они им нужны. Это добавляет к общей памяти, которую PHP должен использовать для каждого запуска.

Также проекты, использующие Frames или IFrames, так как это может легко удвоить использование памяти.

Так что используйте условную загрузку ваших файлов классов, не загружайте ничего, что вы не используете

Не получаю сообщения компилятора для веток if/else:

if( $foo )
{
  some_function();
}
else
{
  non_existing_function();   // oops!
}

PHP не будет упоминать это non_existing_function не существует, пока вы не войдете в ситуацию, когда $foo ложно


Забыв установить:

error_reporting( E_ALL );

Так что уведомления не ловятся, тратя время на отладку:

  • несуществующие переменные
  • неверные свойства объекта
  • неверные ключи массива

Склейка строк из разных "типов" / источников без экранирования:

// missing mysql_real_escape_string() or an int cast !
$sql = "SELECT * FROM persons WHERE id=$id";

// missing htmlentities() and urlencode() !
$html = "<a href='?page=$id'>$text</a>";  

Еще одна ловушка в PHP, я видел эту ошибку от людей, которые приходят из других языков, но не часто.

<?php
/**
 * regular
 */
echo (true && true); // 1
echo (true && false); // nothing

echo (true || false); // 1
echo (false || false); // nothing

echo (true xor false); // 1
echo (false xor false); // nothing

/**
 * bitwise
 */
echo (true & true); // 1
echo (true & false); // 0

echo (true | false); // 1
echo (false | false); // 0

echo (true ^ false); // 1
echo (false ^ false); // 0
?>

Согласно Почему вызов функции (такой как strlen, count и т. Д.) Для указанного значения так медленен?

Если вы передаете переменную в функцию по ссылке, а затем вызываете функцию, это невероятно медленно.

Если вы перебираете вызов функции, а переменная велика, она может быть на много порядков медленнее, чем если бы переменная передавалась по значению.

Пример:

<?php
function TestCount(&$aArray)
{
    $aArray = range(0, 100000);
    $fStartTime = microtime(true);

    for ($iIter = 0; $iIter < 1000; $iIter++)
    {
        $iCount = count($aArray);
    }

    $fTaken = microtime(true) - $fStartTime;

    print "took $fTaken seconds\n";
}

$aArray = array();
TestCount($aArray);
?>

Это последовательно занимает около 20 секунд для запуска на моем компьютере (на PHP 5.3).

Но если я изменю функцию для передачи по значению (т.е. function TestCount($aArray) вместо function TestCount(&$aArray)), тогда он работает примерно за 2 мс - буквально в 10000 раз быстрее!

То же самое верно для любой функции, которая проходит по значению - обе встроенные функции, такие как strlenи для пользовательских функций.

Это довольно страшный брезент, о котором я раньше не знал!

К счастью, существует простой обходной путь, который применим во многих случаях - используйте временную локальную переменную внутри цикла и скопируйте в ссылочную переменную в конце.

Проблемы с производительностью PHP-приложений обычно связаны с одной из следующих причин:

  • Доступ к файловой системе - чтение и запись на диск
    • Здесь пригодятся APC, eAccelerator и т. Д., Которые уменьшают доступ к файловой системе, кэшируя проанализированные PHP-файлы в памяти
  • База данных - медленные запросы, большие наборы данных
  • Сетевой ввод / вывод - доступ к внешним ресурсам

Очень редко можно столкнуться с проблемами производительности с PHP (или любым веб-приложением, написанным на любом языке). Вышеуказанные проблемы обычно на несколько порядков медленнее, чем выполнение кода.

Как всегда, профилируйте свой код!

$x = array();
$x == null ? "true": "false";

Вывод "правда".

$x = array("foo");
$x == null ? "true": "false";

Выход "ложь";

Просто подумал еще об одном сюрпризе. array_map, который применяет обратный вызов к массиву, является серьезным фактором снижения производительности. Я не совсем уверен, почему, но я думаю, что это как-то связано с механизмом копирования и записи PHP для циклов.

Типизация и тройной равный

Обычно в большинстве языков, когда вы оперируете двумя различными типами данных, вы либо получаете исключение, либо один из них приводится к более общему. В языке, за исключением PHP, строка считается более общей, чем целая. Только в PHP у вас есть:

php > var_dump('nada' == 0);
bool(true)

Чтобы справиться с этим в PHP введен оператор тройного равенства. Который по определению возвращает true, если значения имеют одинаковый тип и одинаковое значение. Работает для примера выше:

php > var_dump('nada' === 0);
bool(false)

Но это также ведет себя довольно уродливо, когда вы действительно хотите, чтобы значения были равны.

php > var_dump(0.0 === 0);
bool(false)

Если вы собираетесь работать с PHP с опытом работы с любым другим языком, у вас обязательно будут проблемы с этим.

В самом начале можно было потратить много времени на отладку такого кода:

$a = 1;
echo $a;      # 1
echo "$a";    # 1
echo '$a';    # $a

чертовски цитаты! очень расстраивает:(

Другие вопросы по тегам