Частые ошибки php

PHP делает относительно легкой разработку систем на web платформе, что является основной причиной его популярности. Но не смотря на его простоту использования, PHP превратился в довольно сложный язык со множеством фреймворков, нюансов и тонкостей, которые могут “укусить” разработчиков, ведущих к волосо-выдергивающим часам отладки. Эта статья выделяет 10 самых распространенных ошибок, которых PHP разработчики должны остерегаться.

php

Распространенная ошибка #1: Оставление висячих ссылок на массивы после foreach циклов.

Не уверены как испрользовать foreach циклы в PHP? Использование ссылок в foreach циклах может быть полезно, если вы хотите работать с каждым элементом в массиве, который вы обходите. Например:

$arr = array(1, 2, 3, 4);

foreach ($arr as &$value) {

    $value = $value * 2;

}

// $arr теперь array(2, 4, 6, 8)

Проблема в том, что если вы не осторожны, это может иметь нежелательные побочные эффекты и последствия. А именно, в приведенном примере, после того как код выполнен, $value останется в области видимости и будет иметь ссылку на последний элемент в массиве. Последующие операции с участием $value могут, следовательно, непреднамеренно изменить последний элемент в массиве.

Главное запомнить, что foreach не создает область видимости. Таким образом, $value в приведенном примере это ссылка в пределах текущей области видимости скрипта. На каждой итерации foreach заставляет ссылку указывать на следующий элемент массива $array. После завершения цикла, следовательно, $value все еще указывает на последний элемент в $array и остается в текущей области видимости.

Вот пример таких уклончивых и запутанных ошибок, к которым это может привести:

$array = [1, 2, 3];

echo implode(‘,’, $array), «n»;

foreach ($array as &$value) {}    // по ссылке

echo implode(‘,’, $array), «n»;

foreach ($array as $value) {}     // по значению (копия)

echo implode(‘,’, $array), «n»;

Вышеприведенный код выведет следующее:

Нет, это не опечатка. Последнее значение в последней строке действительно 2, а не 3.

Почему?

После прохождения первого цикла foreach, $array остается неизменным, но, как объясняется выше, $value остается как висячая ссылка на последний элемент в $array (так как цикл foreach обращался к $value по ссылке).

Как результат, когда мы проходим через второй foreach цикл, “странные вещи” начинают происходить. А именно, так как доступ к $value теперь будет осуществляться по значению (т.е. как копия), foreach копирует каждый последовательный элемент $array в переменную $value на каждом шаге цикла. Как результат, вот что происходит на каждом последующем шаге foreach цикла:

  • Проход 1: Копирует $array[0] (т.е., “1”) в $value (которая является ссылкой на $array[2]), поэтому $array[2] теперь равняется 1. Поэтому $array теперь содержит [1, 2, 1].
  • Проход 2: Копирует $array[1] (т.е., “2”) в $value (которая является ссылкой на $array[2]), поэтому $array[2] теперь равняется 2. Поэтому $array теперь содержит [1, 2, 2].
  • Проход 3: Копирует $array[2] (который теперь равняется “2”) в $value (которая является ссылкой на $array[2]), поэтому $array[2] остается равным 2. Поэтому $array теперь содержит [1, 2, 2].

Чтобы все-таки получить выгоду от использования ссылок в foreach циклах без риска получить такие вот проблемы, вызовите unset() на переменную непосредственно после foreach цикла, чтобы удалить ссылку, нарпимер:

$arr = array(1, 2, 3, 4);

foreach ($arr as &$value) {

    $value = $value * 2;

}

unset($value);   // $value больше не ссылается на $arr[3]

Растространенная ошибка #2: Недопонимание поведения isset()

Не смотря на свое название,  isset() возвращает ложь не только если параметр не существует, но так же возвращает ложь для null значений.

Это поведение более проблематично чем может показаться сначала и это распространенный источник проблем.

Рассмотрим следующее:

$data = fetchRecordFromStorage($storage, $identifier);

if (!isset($data[‘keyShouldBeSet’]) {

    // сделать что-то тут если ‘keyShouldBeSet’ не инициализирован

}

Автор этого кода предположительно хотел проверить, что keyShouldBeSet был инициализирован в $data. Но, как отмечалось, isset($data[‘keyShouldBeSet’]) будет всегда возвращать ложь если $data[‘keyShouldBeSet’] был инициилирован, но был установлен в null. Поэтому вышеописанная логика имеет недостаток.

Вот еще один пример:

if ($_POST[‘active’]) {

    $postData = extractSomething($_POST);

}

// …

if (!isset($postData)) {

    echo ‘post not active’;

}

Код выше подразумевает, что если $_POST[‘active’] возвращает истину, то postData будет обязательно инициализирован и поэтому isset($postData) будет возвращать истину. Таким образом, напротив, код выше предполагает, что единственный способ по которому isset($postData) будет возвращать ложь, это если $_POST[‘active’] так же возвращает ложь.

Нет.

Как объяснено, isset($postData) будет всегда возвращать ложь если $postData установлен в null. И, таким образом, это возможно для isset($postData) вернуть ложь даже если $_POST[‘active’] возвращает истину. Итак, еще раз, вышеописанная логика имеет недостаток.

И кстати, как побочный эффект, если намерение в вышеописанном коде на самом деле еще раз проверить, что  $_POST[‘active’] возвращает истину, полагаться на isset() для этого было плохим решением кодирования в любом случае. Вместо этого было бы лучше просто перепроверить $_POST[‘active’], т.е.:

if ($_POST[‘active’]) {

    $postData = extractSomething($_POST);

}

// …

if (!isset($postData)) {

    echo ‘post not active’;

}

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

Для примера мы можем переписать первый из вышеприведенных примеров как показано:

$data = fetchRecordFromStorage($storage, $identifier);

if (! array_key_exists(‘keyShouldBeSet’, $data)) {

    // делать это если ‘keyShouldBeSet’ не утсановлен

}

Кроме того, при объединении 
array_key_exists()
 с get_defined_vars() мы можем достоверно проверить является ли переменная в текущей области видимости объявленной или нет:

if (array_key_exists(‘varShouldBeSet’, get_defined_vars())) {

    // переменная $varShouldBeSet существует в текущей области видимости

}

Растространенная ошибка #3: Путанница между возвратом по ссылке и возвратом по значению

Рассмотрим этот пример кода:

class Config

{

    private $values = [];

    public function getValues() {

        return $this->values;

    }

}

$config = new Config();

$config->getValues()[‘test’] = ‘test’;

echo $config->getValues()[‘test’];

Если вы выполните вышеприведенный код, вы получите следующее:

PHP Notice:  Undefined index: test in /path/to/my/script.php on line 21

Что не правильно?

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

Таким образом вышеприведенный вызов к getValues() вернет копию массива $values, а не ссылку на него. Имея это в виду, давайте пересмотрим две строки в приведенном примере:

// getValues() возвращает КОПИЮ массива $values, таким образом это добавляет элемент ‘test’

// в КОПИЮ массива $values, но не в исходный массив $values.

$config->getValues()[‘test’] = ‘test’;

// getValues() снова возвращает ЕЩЕ ОДНУ КОПИЮ массива $values, и ЭТА копия не

// содержит элемент ‘test’ (вот почему мы получаем сообщение «undefined index»).

echo $config->getValues()[‘test’];

Одно из возможных решений это сохранить первую копию массива $values возвращенную из getValues() и далее оперировать этой копией последовательно, т.е.:

$vals = $config->getValues();

$vals[‘test’] = ‘test’;

echo $vals[‘test’];

Этот код будет работать хорошо (т.е. он будет выводить test не генерируея никаких “undefined index” сообщений), но, в зависимости от того, что вы хотите сделать, этот подход может быть или не быть достаточным. В частности, данный код не будет изменять оригинальный массив $values. Поэтому, если вы хотите чтобы ваши изменения (такие как добавление элемента test) затрагивали оригинальный массив, вы должны модифицировать функицю getValues(), чтобы она возвращала ссылку на $values. Это выполняется путем добавления & перед именем функции, таким образом указывается, что должна возвращаться ссылка, т.е.:

class Config

{

    private $values = [];

    // вернуть ССЫЛКУ на оригинальный массив $values

    public function &getValues() {

        return $this->values;

    }

}

$config = new Config();

$config->getValues()[‘test’] = ‘test’;

echo $config->getValues()[‘test’];

Будет выводиться test, как ожидалось.

Но чтобы сделать вещи более запутанными, рассмотрим вместо этого следующий пример кода:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

class Config

{

    private $values;

    // используем ArrayObject вместо массива

    public function __construct() {

        $this->values = new ArrayObject();

    }

    public function getValues() {

        return $this->values;

    }

}

$config = new Config();

$config->getValues()[‘test’] = ‘test’;

echo $config->getValues()[‘test’];

Если вы предполагаете, что это будет выводить такую же “undefined index” ошибку, как наш ранний пример с массивом, то вы ошибаетесь. Причина в том, что в отличии от массивов, PHP всегда передает объекты по ссылке. (ArrayObject это SPL объект, который полностью повторяет функционал массива, но работает как объект).

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

Во всем сказанном стоит заметить, что практика возврата ссылки на массив или ArrayObject в общем это то, что должно избегаться, т.к. это предоставляет клиенту возможность модифицировать приватные данные экземпляра. Это “плевок в лицо” инкапсуляции. Вместо этого лучше использовать старомодные геттеры и сеттеры, например:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

class Config

{

    private $values = [];

    public function setValue($key, $value) {

        $this->values[$key] = $value;

    }

    public function getValue($key) {

        return $this->values[$key];

    }

}

$config = new Config();

$config->setValue(‘testKey’, ‘testValue’);

echo $config->getValue(‘testKey’);    // echos ‘testValue’

Этот подход дает клиенту возможность установить любое значение в массив без предоставления публичного доступа к, в противном случае приватному, массиву $values.

Растространенная ошибка #4: Выполнение запросов в цикле

Это не редкость натолкнуться на что-то вроде этого, если ваш PHP не работает:

$models = [];

foreach ($inputValues as $inputValue) {

    $models[] = $valueRepository->findByValue($inputValue);

}

Пока тут может быть абсолютно ничего не верного, но если вы последуете по логике кода, вы можете обнаружить, что этот невинно выглядящий вызов $valueRepository->findByValue() в конечном счете приводит в результате к запросу, похожему на:

$result = $connection->query(«SELECT `x`,`y` FROM `values` WHERE `value`=» . $inputValue);

Как результат, каждая итерация приведенного цикла будет вызывать отдельный запрос в базу данных. Так если, для примера, вы передаете массив с 1000 значений в в цикл, будет сгенерировано 1000 отдельных запросов к ресурсу! Если подобный скрипт вызыван в нескольких потоках, это может потенциально привести систему к полной остановке.

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

Один пример довольно распространненого случая где можно встретить неэффективные запросы (т.е. в цикле), это когда форма публикуется со списком значений (ИД для примера). Тогда, чтобы получить полный набор данных для каждого ИД код будет итерировать через массив и делать отдельный SQL запрос для каждого ИД. Это обычно выглядит как-то так:

$data = [];

foreach ($ids as $id) {

    $result = $connection->query(«SELECT `x`, `y` FROM `values` WHERE `id` = « . $id);

    $data[] = $result->fetch_row();

}

Но того же можно добиться гораздо более эффективно в одном запросе, как показано:

$data = [];

if (count($ids)) {

    $result = $connection->query(«SELECT `x`, `y` FROM `values` WHERE `id` IN (« . implode(‘,’, $ids));

    while ($row = $result->fetch_row()) {

        $data[] = $row;

    }

}

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

Растространенная ошибка #5: Сложности и неэффективность использования памяти

Пока получение множества записей одновременно определенно более эффективнее, чем выполнение одного запроса для получения каждой записи, такой подход может потенциально вести к состоянию “out of memory”  в libmysqlclient когда используется PHP mysql расширение.

Чтобы продемонстрировать, давайте взглянем на тестовую платформу с ограниченынми ресурсами (512MB RAM), MySQL, и 
phpcli.

Мы инициализируем таблицу базы данных вот так:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

// подключиться к mysql

$connection = new mysqli(‘localhost’, ‘username’, ‘password’, ‘database’);

// создать таблицу с 400 столбцами

$query = ‘CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT’;

for ($col = 0; $col < 400; $col++) {

    $query .= «, `col$col` CHAR(10) NOT NULL»;

}

$query .= ‘);’;

$connection->query($query);

// добавить 2 миллиона записей

for ($row = 0; $row < 2000000; $row++) {

    $query = «INSERT INTO `test` VALUES ($row»;

    for ($col = 0; $col < 400; $col++) {

        $query .= ‘, ‘ . mt_rand(1000000000, 9999999999);

    }

    $query .= ‘)’;

    $connection->query($query);

}

OK, теперь давайте проверим использование ресурсов:

// подключиться к mysql

$connection = new mysqli(‘localhost’, ‘username’, ‘password’, ‘database’);

echo «Before: « . memory_get_peak_usage() . «n»;

$res = $connection->query(‘SELECT `x`,`y` FROM `test` LIMIT 1’);

echo «Limit 1: « . memory_get_peak_usage() . «n»;

$res = $connection->query(‘SELECT `x`,`y` FROM `test` LIMIT 10000’);

echo «Limit 10000: « . memory_get_peak_usage() . «n»;

Вывод:

Before: 224704

Limit 1: 224704

Limit 10000: 224704

Круто. Похоже что запрос безопасно выполняется в рамках границ ресурсов.

Все же, просто чтобы убедиться, давайте поднимем предел еще раз и установим его в 100,000. Ой-ой. Когда мы сделали это, мы получили:

PHP Warning:  mysqli::query(): (HY000/2013):

              Lost connection to MySQL server during query in /root/test.php on line 11

Что произошло?

Проблема тут в том, как работает PHP mysql модуль. Это на самом деле просто прокси к libmysqlclient, которая делает грязную работу. Когда порция данных выбрана, она помещается прямо в память. Так как это использование памяти не управляется PHP менеджером, memory_get_peak_usage() не будет показывать ни какого увеличения в использовании ресурсов если мы поднимем лимит в нашем запросе. Это ведет к проблемам, таким, как одна уже показанная ранее, когда мы обманчиво удовлетворены, думая что наше управление памятью в порядке. Но в реальности наше управление памятью серьезно уязвлено и мы можем получить проблемы такие, как показанная ранее.

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

Таким образом, если мы выполним приведенный выше тест используя mysqlnd вместо mysql, мы получим гораздо более реалистичную картину потребления памяти:

Before: 232048

Limit 1: 324952

Limit 10000: 32572912

Кстати, есть нечто более худшее, чем это. Согласно с PHP документацией mysql использует в два раза больше ресурсов, чем mysqlnd, для сохранения данных, так что оригинальный скрипт реально использовал даже больше памяти, чем показано здесь (примерно в два раза больше).

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

$totalNumberToFetch = 10000;

$portionSize = 100;

for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) {

    $limitFrom = $portionSize * $i;

    $res = $connection->query(

                         «SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize»);

}

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

Растространенная ошибка #6: Игнорирование проблем с Unicode/UTF-8

В каком-то смысле, это реально больше чем проблема в PHP сама по себе, чем что-то с чем вы можете столкнуться во время отладки PHP, но она никогда не была достаточно рассматриваема. Ядро PHP 6  было сделано знающим Unicode, но это было приостановлено, когда разработка PHP 6 была остановлена еще в 2010.

Но это ни в коем случае не освобождает разраработчика от правильного обращения с UTF-8 и избегания ошибочного предположения, что все строки обязательно будут в “старом добром ASCII“. Код, который не может правильно обращаться с не-ASCII строками печально известен внесением грубых гейзенбагов  (heisenbugs) в ваш код. Даже простой вызов strlen($_POST[‘name’]) может вызвать проблемы если кто-то с фамилией “Schrödinger” попытается войти в вашу систему.

Вот не большой список для проверки, чтобы избежать такие проблемы в вашем коде:

  • Если вы не знаете достаточно про Unicode и UTF-8, вы должны по крайней мере изучить основы. Вот прекрасный пример тут.
  • Убедитесь, что всегда используете 
    mb_* функции вместо старых строковых функций (убедитесь, что расширение “multibyte” включено в вашу PHP сборку)
  • Убедитесь что база данных и таблицы используют Unicode (многие сборки MySQL все еще используют latin1 по умолчанию)
  • Запомните, что json_encode() конвертирует не-ASCII символы (например,“Schrödinger” становится“Schru00f6dinger”), но serialize() нет.
  • Убедитесь, что ваши PHP файлы так же в UTF-8 кодировке, чтобы избежать коллизий, когда соединяете строки с закодированными или сконфигурированными строковыми константами.

Особенно важный ресурс в этом отношении это UTF-8 Primer for PHP and MySQL пост от Francisco Claria в его блоге.

Растространенная ошибка #7: Предполагать, что $_POST будет всегда содержать ваши POST данные

Не смотря на свое имя, массив $_POST не будет всегда содержатб ваши POST данные и может быть легко обнаружен пустым. Чтобы понять это, давайте рассмотрим пример. Представьте, что мы сделали запрос на сервер используя вызов jQuery.ajax() как показано:

// js

$.ajax({

    url: ‘http://my.site/some/path’,

    method: ‘post’,

    data: JSON.stringify({a: ‘a’, b: ‘b’}),

    contentType: ‘application/json’

});

(Между прочим заметьте, что contentType: ‘application/json’. Мы посылаем данные как JSON, что достаточно популярно для API. Это по умолчанию, для примера, для отправки запросов в AngularJS
$http service.)

На стороне сервера в нашем примере мы просто выводим дамп массива $_POST:

Неожиданно результат будет:

Почему? Что случилось с нашей JSON строкой {a: ‘a’, b: ‘b’}?

Ответ в том, что PHP парсит POST запрос автоматически когда он имеет тип контента application/x-www-form-urlencoded или multipart/form-data. Причины для этого исторические –  только эти два типа контента фактически использовались когда PHP $_POST был реализован. Поэтому с любым другим типом контента (даже с теми что достаточно популярны сегодня, такими как application/json), PHP не распознает автоматически POST запрос.

Так как $_POST суперглобальный, если мы переопределим его один раз (предпочтительно ранее в нашем скрипте), модифицированное значение (т.е. включая POST данные) будет доступно по ссылке в нашем коде. Это важно так как $_POST обычно используется PHP фреймворками и почти всеми привычными скриптами для извлечения и преобразования данных запроса.

Так, для примера, когда обрабатывается POST запрос с типом контента application/json, мы должны в ручную распарсить контент запроса (т.е. декодировать JSON данные) и перезаписать переменную $_POST, как показано:

// php

$_POST = json_decode(file_get_contents(‘php://input’), true);

Затем когда мы выведем массив $_POST, мы увидем что он корректно включает POST данные, на пример:

array(2) { [«a»]=> string(1) «a» [«b»]=> string(1) «b» }

Растространенная ошибка #8: Думать, что PHP поддерживает символьный тип данных

Взгляните на этот простой кусок кода и попробуйте догадаться что он выведет:

for ($c = ‘a’; $c <= ‘z’; $c++) {

    echo $c . «n»;

}

Если ответили от “а” до “z” вы может будете удевлены узнать, что вы ошиблись.

Да, он вывоедет от “а” до “z”, но так же он выведет от “аa” до “yz”. Давайте посмотрим почему.

В PHP нет символьного типа данных; только строковый доступен. Имея это в виду, приращение строки “z”  в PHP производит “aa”:

php> $c = ‘z’; echo ++$c . «n»;

aa

До того как конфуз станет больше, aa лексографически меньше чем z:

hp> var_export((boolean)(‘aa’ < ‘z’)) . «n»;

true

Вот почему приведенный код представляет вывод букв от “а” до “z”, но так же выводит от “аa” до “yz”. Он остановится, когда достигнет za, это будет первое значение больше чем z, которое будет встречено:

php> var_export((boolean)(‘za’ < ‘z’)) . «n»;

false

В таком случае, вот один способо правильно обойти значения от “а” до “z” в PHP:

for ($i = ord(‘a’); $i <= ord(‘z’); $i++) {

    echo chr($i) . «n»;

}

Или по другому:

$letters = range(‘a’, ‘z’);

for ($i = 0; $i < count($letters); $i++) {

    echo $letters[$i] . «n»;

}

Растространенная ошибка #9: Игнорирование стандартов кодирования

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

Игнорирование стандартов кодирования может причинить множество проблем в проекте. В лучшем случае, это выльется в код, который является непоследовательным (если каждый разработчик “делает свои собственные штуки”).  Но в худшем, это создаст PHP код, который не работает или может быть сложным (иногда практически невозможным) в навигации, делая экстремально сложной отладку, доработку, поддержку. И это означает пониженную производительность для вашей команды, включая множество потраченных (или по крайней мере не нужных) усилий.

К счастью для PHP программистов есть PHP Standards Recommendation (PSR) включающий следующие пять стандартов (не переводится, т.к. имена стандартов) :

  • PSR-0: Autoloading Standard
  • PSR-1: Basic Coding Standard
  • PSR-2: Coding Style Guide
  • PSR-3: Logger Interface
  • PSR-4: Autoloader

PSR в оригинале был сделан на основе вкладов от сопровождающих самых узнаваемых платформ на рынке. Zend, Drupal, Symfony, Joomla и другие внесли вклад в эти стандарты и сейчас следуют им.
Даже PEAR, который пытался быть стандартом на протяжении годов до этого, участвует в PSR сейчас.

В каком-то смысле это почти не имеет значения каковы ваши стандарты кода, до тех пор, пока вы согласы на стандарт и придерживаетесь его, но следование PSR это в общем хорошая идея, до тех пор пока вы не имеете убедительных оснований в вашем проекте делать иначе. Больше и больше команд и проектов соответствуют PSR. Он определенно признается сейчас КАК стандарт для большинства PHP разработчиков, так что его использование поможет убедиться, что новые разработчики знакомы и им комфортно с вашим стандартом кодирования, когда они войдут в вашу команду.

Растространенная ошибка #10: Не правильное использование empty()

Некоторые PHP разработчики любят использовать empty() для логических проверок просто для чего угодно. Однако, есть случаи где это может привести к конфузу.

Во первых, давайте вернемся к массивам и экземплярам ArrayObject (которые повторяют массивы). Взяв их простоту легко предположить, что массивы и экземпляры ArrayObject будут вести себя одинаково. Это оказывается, тем не менее, опасным предположением. Для примера в PHP 5.0:

// PHP 5.0 или новее:

$array = array();

var_dump(empty($array));        // выводит bool(true)

$array = new ArrayObject();

var_dump(empty($array));        // выводит bool(false)

// почему оба вывода не идентичны?

И, чтобы сделать дела еще хуже, результаты были бы другими до PHP 5.0:

// До PHP 5.0:

$array = array();

var_dump(empty($array));        // выводит bool(false)

$array = new ArrayObject();

var_dump(empty($array));        // выводит bool(false)

Этот подход тем не менее достаточно популярен. Для примера это способ, которым 
<a href=«http://framework.zend.com/manual/2.3/en/modules/zend.db.table-gateway.html» target=«_blank»>ZendDbTableGateway</a> в Zend Framework 2 возвращает данные, когда вызывается 
current()
 на результате TableGateway::select() как рекомендует документация. Разработчик может легко стать жертвой этой ошибки с похожими данными.

Чтобы избежать этих проблем, лучший подход для проверки пустых структур массивов это использовать count():

// Заметьте, что это работает во ВСЕХ версиях PHP (до и после 5.0):

$array = [];

var_dump(count($array));        // выводит int(0)

$array = new ArrayObject();

var_dump(count($array));        // выводит int(0)

И идентично, т.к. PHP приводит 0 к false, count() может быть так же использован с if() конструкциями для проверки пустых массивов. Так же стоит упомянуть, что в PHP count() имеет постоянную сложность (O(1) операций) на массивах, что делает это даже более ясным, что это верный выбор.

Другой пример когда empty() может быть опасным, это когда оно комбинируется с магической функцией __get(). Давайте определим два класса и свойство test в обоих.

Сперва давайте определим класс Regular который включает test как нормальное свойство:

class Regular

{

public $test = ‘value’;

}

Далее давайте определим класс Magic, который использует магический оператор __get() для доступа к свойству test:

class Magic

{

private $values = [‘test’ => ‘value’];

public function __get($key)

{

if (isset($this->values[$key])) {

return $this->values[$key];

}

}

}

ОК, теперь давайте посмотрим что получится, когда мы попытаемся получить доступ к свойству test в каждом из этих классов:

$regular = new Regular();

var_dump($regular->test);    // выводит string(4) «value»

$magic = new Magic();

var_dump($magic->test);      // выводит string(4) «value»

Очень хорошо.

Но теперь давайте посмотрим что произойдет, когда мы вызовем empty() в каждом случае:

var_dump(empty($regular->test));    // outputs bool(false)

var_dump(empty($magic->test));      // outputs bool(true)

Омг. Так, если мы пологаемся на empty(), мы можем быть введены в заблуждение веря, что свойство test в $magic пустое, когда в реальности оно равно ‘value’.

К сожалению, если класс использует магическую функцию __get() для получения значения свойства, нет безопасного пути проверить это свойство пустое или нет. Снаружи области видимости класса вы можете проверить только будет ли возвращено значение null, и это не обязательно означает, что соответствующий ключ не установлен, т.к. он на самом деле может быть установлен в null.

В противоположность, если мы попытаемся сослаться на не существующее значение в класса Regular, мы получим уведомление как это:

Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10

Call Stack:

    0.0012     234704   1. {main}() /path/to/test.php:0

Так что главная идея это то, что метод empty() должен быть использован с осторожностью, т.к. он может привести к заблуждающим – или даже потенциально обманчивым – результатам, если кто-то не осторожен.

Заключение

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

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

Оригинал на английском:

http://www.toptal.com/php/10-most-common-mistakes-php-programmers-make

Это мой перевод. Если вы заметили ошибки или считаете, что какие-то части можно перевести лучше – пишите в комментарии.

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

  • Введение
  • Об авторе
  • О переводчике

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

Как правило, дело в недостаточной практике программирования. Неопытные программисты становятся перед лицом необходимости создания сложных веб-приложений. Поэтому сплошь и рядом допускаются ошибки, которых избежал бы опытный программист, такие как необоснованное использование функции printf()или неправильное использование семантики PHP.

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

Часть 1: Описываются 7 <детских> ошибок (№ 21-15, в обратном порядке, в соответствии со степенью серьезности по нашей классификации). Такие ошибки не вызывают серьезных проблем, но приводят к уменьшению эффективности работы программы, а также выражаются в громоздком трудночитаемом коде, в который, к тому же, трудно вносить изменения.

Часть 2: Следующие 7 ошибок (№ 14-8) относятся к <серьезным>. Они ведут к еще более значительному уменьшению скорости выполнения кода, уменьшению безопасности скриптов; код становится еще более запутанным.

Часть 3: Описания семи, последних, <смертельных> ошибок. Эти ошибки концептуальны по своей природе и являются причиной появления ошибок, описанных в 1-ой и 2-ой частях статьи. Они включают и такие ошибки, как недостаточное внимания, уделенное как проекту в целом, так и коду программы, в частности.

21. Неоправданное использование функции printf()

Функция printf() предназначена для вывода форматированных данных.

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

Ниже приведен пример обоснованного применения функции printf(). В данном случае она используется для форматированного вывода числа <пи>:

<?php

printf ("Число Пи: %2fn<br>n", M_PI);
printf ("Это тоже число Пи: %3fn<br>n", M_PI;
printf ("И это Пи: %4fn<br>n", M_PI);

?>

Примечание: Наблюдаются случаи патологической боязни функции printf(), когда люди пишут свои функции форматированного вывода, порой по 30-40 строк, хотя все проблемы мог бы решить один-единственный вызов функции printf().

Многие программисты используют функцию printf() для вывода переменных, результатов вызова функций, а иногда даже обычных текстовых данных. Наиболее часто это происходит в следующих двух случаях:

* когда следовало бы использовать функцию print();
* при выводе результатов, возвращаемых функциями.

Когда следует использовать print()

Вызов функции printf() зачастую используется там, где следовало бы использовать print(). В следующем примере функция printf() используется для вывода четырех переменных:

<?php

 $name = 'Sterling Hughes';
 $job = 'Senior Engineer';
 $company = 'DesignMultimedia';
 $email = 'shughes@designmultimedia.com';

 printf ( "Меня зовут %sn<br>n
 Я работаю %s, %sn<br>n
 Мой адрес E-mail:%sn<br>n",
 $name, $job, $company, $email );
?>

В данном случае возможно (и желательно!) применение print():

print «Меня зовут $namen<br>n
Я работаю в $company, $jobn<br>n
Мой адрес E-mail: $emailn<br>n»;

Использование print() вместо printf() в случаях, когда выводятся неформатированные данные, как в данном примере, дает следующие выгоды:

* Увеличение производительности: Функция printf() форматирует свои аргументы перед выводом. Таким образом, время ее выполнения больше, чем для функций print() или echo().
* Более ясный код: Все-таки, надо признать, что использование функции printf() затрудняет чтение кода (имеющих достаточный опыт программирования на C, это, конечно, касается в меньшей степени). Чтобы функция printf() не повела себя самым неожиданным для вас образом, требуется как знание синтаксиса данной функции, (т.е. %s определяет строковый формат вывода, тогда как %d — десятичный), так и знание типов переменных.

Использование функции printf() для вывода значения, возвращаемого функцией

Еще одна характерная ошибка использования функции printf() — вывод значения, возвращаемого функцией, как в следующем примере:

<?php

 printf ("Найдено %d вхождений строки %s", count ($result), $search_term);

?>

Наряду с функцией print(), при использовании ее в тех же целях, следует использовать оператор ‘.’ В данном случае этот оператор добавляет текст к результату вызова функции:

<?php

 print "Найдено " .
 count ($result) .
 " вхождений строки $search_term";

?>

Использование оператора . в паре с функцией print() позволяет избежать использования более медленной функции printf().

20. Неверное применение семантики языка

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

* Синтаксис PHP:Представляет собой набор правил для определения элементов языка. Например, как мы определяем переменную? Ставим знак $ перед ее именем. Как определяем функцию? В общем случае, используя скобки, аргументы и т.п.
* Семантика PHP: Представляет собой набор правил для применения синтаксиса. Например, возьмем функцию с двумя аргументами, что определяется ее синтаксисом. Причем в качестве аргументов ей следует передавать переменные строкового типа ?- это определяется семантикой.

Заметьте: <следует>. В языках с четким разделением типов (таких как Java или C) нет понятия <следует> (в общем случае, хотя бывают и исключения). В таком случае компилятор вынудит использовать переменные строго определенного типа.

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

Возьмем кусок кода, который открывает файл и выводит его построчно:

<?php

$fp = @fopen ( 'somefile.txt', 'r' )
 or die ( 'Не могу открыть файл somefile.txt' );

 while ($line = @fgets ( "$fp", 1024)) // Здесь ошибка!
 {
 print $line;
 }

@fclose ("$fp") // И здесь тоже color
 or die( 'Не могу закрыть файл somefile.txt' );

?>

В данном случае появится сообщение об ошибке типа:
«Warning: Supplied argument is not a valid File-Handle resource in tst.php on line 4»
(«Внимание: аргумент не может являться дескриптором файла»)

Это вызвано тем, что переменная $fp заключена в двойные кавычки, что однозначно определяет ее как строку, тогда как функция fopen() ожидает в качестве первого аргумента дескриптор, но не строку. Соответственно, вам следует использовать переменную, которая может содержать дескриптор.

Примечание: В данном случае, строковый тип допустим синтаксически.

Для решения проблемы следует просто убрать двойные кавычки:

<?php
 $fp = @fopen ( 'somefile.txt', 'r' )
 or die ( 'Не могу открыть файл somefile.txt' );

 while ( $line = @fgets ($fp, 1024) )
 {
 print $line;
 }

 @fclose ($fp)
 or die ( 'Не могу закрыть файл somefile.txt' );
?>

Как избежать неправильного приложения семантики?

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

Но будьте осторожны, заигрывая с семантикой языка! Возможно появление трудноуловимых ошибок в программах. Если же вы все-таки решили поэкспериментировать, вам следует понимать три ключевых момента:

* Типы: В PHP каждая переменная в любой момент времени относится к определенному типу. И это несмотря на тот факт, что ее тип можно свободно изменять. Другими словами, в языке PHP переменная не может существовать, при этом не относясь к определенному типу (и, соответственно, не обладая характеристиками, присущих этому типу). В PHP есть 7 основных типов переменных: Boolean, resource, integer, double, string, array и object.
* Область видимости: В PHP переменные имеют область видимости, которая определяет то, откуда она может быть доступна, и насколько долго будет существовать. Недопонимание концепции <области видимости> может проявляться в виде различного рода <плавающих> ошибок.
* php.ini: При написании кода следует понимать, что не все пользователи имеют такую же конфигурацию программно-аппаратных средств, как и вы. Таким образом, совершенно необходимо лишний раз убедиться, сохраняется ли работоспособность вашего кода в той конфигурации, в которой программа должна работать, а не в той, в которой разрабатывалась.

19. Недостаточно либо излишне комментированный текст

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

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

<?php // Начало кода

$age = 18; // Возраст равен 18
$age++; // Увеличим $age на один год

// Напечатаем приветствие
print "Вам сейчас 19 лет, и это значит, что Вам уже было:";
print "n<br>n<br>n";

// Цикл 'для' чтобы вывести все
// предыдущие значения возраста
for ($idx = 0; $idx < $age; $idx++)
 {
 // Напечатаем каждое значение возраста
 print "$idx летn<br>n";
 }
// Конец кода
?>

И все-таки: где золотая середина?

Итак, какой же объем комментариев следует помещать в скрипт?! Это зависит от многого: от времени, которым вы располагаете, от политики компании, сложности проекта и т.д. Тем не менее, запомните несколько основных принципов, которым надо следовать при написании программ вне зависимости от вашего решения:

* Перед телом функции всегда помещайте комментарий — назначение функции.
* Добавляйте комментарии в сомнительных участках кода, когда нет уверенности, что он будет работать как надо.
* Если назначение кода неочевидно, внесите информацию о предназначении этого участка. Вы же потом воспользуетесь этим комментарием.
* Избегайте комментарии вида # — используйте только /* */ либо //.

Следующий пример иллюстрирует хороший стиль комментариев:

<?php
 // Random_Numbers.lib
 // Генерация случайных чисел различного типа

 mt_srand((double)microtime()*1000000);

 //
 // mixed random_element(array $elements[, array weights]) 
 // Возвращает случайный элемент массива-аргумента
 // Массив weights содержит относительные вероятности
 // выборки элементов
 //

 function random_element ($elements, $weights = array())
 {

 // Для корректного функционирования этого алгоритма
 // количество элементов массива должно быть равным
 // количеству элементов массива относительных вероятностей

 if (count ($weights) == count ($elements)) {
 foreach ($elements as $element) {
 foreach ($weights as $idx) {

 // Примечание: мы не используем $idx, потому что
 // нам не нужен доступ к отдельным элементам
 // массива weights

 $randomAr[] = $element;
 }
 }
 }
 else {
 $randomAr = $elements;
 }

 $random_element = mt_rand (0, count ($randomAr) - 1);
 return $randomAr [$random_element];
 }
?>

18. Слишком много переменных — слишком большое время выполнения

Некоторые прямо-таки страдают навязчивой идеей вводить временные переменные где надо и где не надо. Совершенно невозможно понять, чем руководствовался человек, написавший такой код:

<?php
 $tmp = date ("F d, h:i a"); // т.е. формат даты February 23, 2:30 pm
 print $tmp;
?>

Для чего здесь использована временная переменная?! Она просто не нужна:

<?php
 print date ("F d, h:i a");
?>

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

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

Плюсы использования временных переменных

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

Вот пример, в котором не используется лишних переменных:

<?php

// string reverse_characters (string str)
// Переворачивает строку символов

function reverse_characters ($str)
{
 return implode ("", array_reverse (preg_split ("//", $str)));
}

?>

Вызову функции implode() в качестве одного из параметров передается результат выполнения вложенных функций, поэтому такой код трудно прочесть. В данном случае нам может здорово помочь использование временной переменной:

<?php

// string reverse_characters (string str)
// Переворачивает строку символов

function reverse_characters ($str)
{
 $characters = preg_split ("//", $str);
 $characters = array_reverse ($characters);
 return implode ("", $characters);
}

?>

Золотое правило

Если вы думаете, ввести или нет еще одну временную переменную, задайте себе два вопроса:

* Будет ли эта переменная использована хотя бы дважды?
* Значительно ли улучшится с ее введением читаемость кода?

Если на любой из этих вопросов вы ответили <да>, тогда введите временную переменную. Иначе комбинируйте вызовы функций (если это необходимо) и обойдитесь без ее использования.
17. Переписываем стандартные функции

Кое-кто рекомендует переименовывать стандартные функции для того, чтобы программистам на Visual Basic’е проще было перейти к использованию языка PHP:

<?php
 function len ($str) {
 return strlen ($str);
 }
?>

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

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

Ну и, наконец, это замедляет программу. Дело не только в необходимости обработки большего объема кода, но и в том, что для вызова такой пользовательской функции, требуется больше времени, чем для прямого вызова стандартной функции.

Используйте стандартные функций языка!

Иногда так трудно устоять! Ведь программист редко знает сразу весь набор функций — у него обычно нет времени запомнить их все. Почему бы просто не переименовать функцию? Но, повторимся, этого не следует делать в силу изложенных выше причин.

Хорошо бы иметь под рукой справочник по функциям PHP (удобно использовать индексированную версию в формате PDF). И перед тем как написать какую-либо функцию, внимательно посмотреть — не существует ли она уже в списке стандартных функций.

Но следует заметить, что в кодах программ можно встретить пользовательские функции, написанные еще до их введения в качестве стандартных (например, функции сравнения двух массивов). Это не означает, что вы обязательно должны переписать код и заменять их стандартными функциями.
16. Клиентская часть программы не отделяется от серверной части

Многие программисты рекомендуют объединять код HTML (интерпретируемый на стороне клиента) и код PHP (выполняемый сервером) в один большой файл.

Для маленьких сайтов это, возможно, неплохо. Но, когда ваш сайт начнет расти, вы можете столкнуться с проблемами при необходимости добавить какие-либо новые функции. Такой стиль программирования приводит к очень <непослушному> и громоздкому коду.

API функций

Если вы собрались отделить код PHP от HTML кода, у вас есть два варианта. Один способ — создание функций динамического формирования вывода и поместить их в нужное место на веб-странице.

Например, так:

index.php — код страницы

<?php include_once ("site.lib"); ?>
<html>
<head>
 <title><?php print_header (); ?></title>
</head>
<body>
 <h1><?php print_header (); ?></h1>
 <table border="0" cellpadding="0" cellspacing="0"> 
 <tr>
 <td width="25%">
 <?php print_links (); ?>
 </td>
 <td>
 <?php print_body (); ?>
 </td>
 </tr>
 </table>
</body>
</html>

site.lib — Сам код программы

<?php

$dbh = mysql_connect ("localhost", "sh", "pass")
 or die (sprintf ("Не могу открыть соединение с MySQL [%s]: %s",
 mysql_errno (), mysql_error ()));

@mysql_select_db ("MainSite")
 or die (sprintf ("Не могу выбрать базу данных [%s]: %s",
 mysql_errno (), mysql_error ()));

$sth = @mysql_query ("SELECT * FROM site", $dbh)
 or die (sprintf ("Не могу выполнить запрос [%s]: %s",
 mysql_errno (), mysql_error ()));

$site_info = mysql_fetch_object ($sth);

function print_header ()
{
 global $site_info;
 print $site_info->header;
}

function print_body ()
{
 global $site_info;
 print nl2br ($site_info->body);
}

function print_links ()
{
 global $site_info;

 $links = explode ("n", $site_info->links);
 $names = explode ("n", $site_info->link_names);

 for ($i = 0; $i < count ($links); $i++)
 {
 print "ttt
 <a href="$links[$i]">$names[$i]</a>
 n<br>n";
 }
}
?>

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

Плюсы использования API функций

* Относительно чистый, ясный код
* Быстрый код

Минусы использования API функций

* Не настолько наглядно как система шаблонов
* Все-таки, для модификации дизайна требуется некоторое знание PHP

Система шаблонов

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

Пример использования шаблонов:

<html>

 <head>
 <title>%%PAGE_TITLE%%</title>
 </head>

 <body %%BODY_PROPERTIES%%>
 <h1>%%PAGE_TITLE%%</h1>
 <table border="0" cellpadding="0" cellspacing="0">
 <tr>
 <td width="25%">%%PAGE_LINKS%%</td>
 <td>%%PAGE_CONTENT%%</td>
 </tr>
 </table>
 </body>

</html>

Затем пишем программу, просматривающую код шаблона и при выводе заменяющую тэги вида %%:%% нужной информацией.

Примечание: неплохой класс для использования его в системе шаблонов — FastTemplate, его можно скачать с http://www.thewebmasters.net/.

Плюсы использования шаблонов:

* Предельно просто и ясно
* Для изменения шаблонов не требуется знание PHP

Минусы использования шаблонов:

* Более медленный способ — ведь надо сканировать весь шаблон и лишь потом выводить данные
* Сложнее внедрить на практике

15. Использование устаревшего синтаксиса и функций

Некоторые программисты вновь и вновь используют старые библиотеки и старые наработки. Например, код, написанный еще под PHP 2, до сих пор используется с PHP4, хотя уже начиная с версии PHP3 были добавлены стандартные функции, реализующие то же самое.

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

Пример использования старых языковых конструкций:

<?php

 // Старый стиль

 while (1):
 print "5";
 if ( $idx++ == 5 ):
 break;
 endif;
 endwhile;

 // Лучше написать так
 // (впрочем, код можно оптимизировать)

 while (1)
 {
 print "5";
 if ( $idx++ == 5 ) {
 break;
 }
 }

?>

Почему же следует следовать новым стандартам? Причины следующие:

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

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

В этой статье мы рассмотрели первые 7 из 21 наиболее общих ошибок PHP программиста. Как правило, они не нарушают работоспособности программ, но, тем не менее, их следует избегать:

* Необоснованное применение функции printf(): Ее следует использовать только для вывода форматированных данных.
* Неправильное применение семантики языка: Многие программисты не имеют достаточно времени, чтобы разобраться во всех тонкостях языка, что впоследствии выражается в ошибочном коде.
* Плохо комментированный код: Всегда пишите комментарии! Перед каждой функцией указывайте, что делает данная функция, и какие аргументы она требует. Также комментируйте сложные участки кода и внесенные изменения.
* Слишком много временных переменных: Временные переменные хорошо использовать для предотвращения повторного вызова функций или последовательностей функций.
* Изобретаем велосипед — переписываем стандартную функцию: Сначала загляните в руководство по PHP — не описана ли там функция, которую вы собираетесь написать для, казалось бы, расширения набора стандартных функций PHP.
* Смешан PHP и HTML код: Попробуйте сделать код как можно более модульным. Потом вам (и другим тоже) можно будет сменить дизайн страницы без изменения кода PHP.
* Используются старые языковые конструкции и устаревшие функции: То, что вы можете сделать, не всегда следует делать. Загляните в документацию и литературу по PHP, как писать правильно. Отличные книги — «Разработка веб-приложений с использованием PHP (Web Application Development with PHP) и «Профессиональный программист PHP (Professional PHP). (Эх, где бы их еще найти! ;)) — прим. переводчика)

Стерлинг Хьюз (Sterling Hughes) — независимый разработчик веб-сайтов, занимается созданием динамических веб-приложений для некоторых крупнейших мировых компаний. Участвовал в создании cURL and SWF расширений PHP с открытым исходным кодом. Его книга, <Настольная книга программиста PHP> (The PHP Developer’s Cookbook), была издана в октябре 2000 года издательством .

Дмитрий Короленко. Ну что о нем много говорить? ;)) Благодаря ему люди, не знающие английского, или знающие, но очень ;)), смогут прочесть весьма познавательную статью Стерлинга Хьюза. О замеченных недостатках перевода просьба сообщать на мыло: lkx2@mail.ru Сразу хочется пояснить, что дословный перевод не был самоцелью, но, тем не менее, стиль и смысл оригинальной статьи по возможности ;)) сохранялись. Все замечания и пожелания приветствуются и будут учтены, ибо готовится перевод второй части этой статьи. Только просьба ногами не пинать ;)) Все же я надеюсь,что если вы до этого места дочитали, то не все так плохо ;))

Антон Шевчук // Web-разработчик

25 PHP

Пожалуй продолжу свою незамысловатую серию 25, в этот раз вспомним о наших ошибках…

  1. Книга по PHP за 2002 год как источник знаний – это уже история, советую “PHP 5. Профессиональное программирование” – Э. Гутманс, С. Баккен – ISBN:5-93286-083-9, иль даже поновее…
  2. Использование web-сервера, где “всё включено” (Denwer и еже с ним) – научитесь сетапить сами, потом успеете перейти на полуфабрикаты
  3. Используем простенький редактор с подсветкой синтаксиса – пора взрослеть и переходить на IDE, и с IDE увеличивается скорость разработки, особенно в больших проектах, где не один десяток классов.
  4. В php.ini включен параметр register_globals – отключаем это ЗЛО, по умолчанию отключен, но Вам он зачем-то понадобился
  5. Реализация “index.php” содержащая приблизительно следующий код: <?php require_once $_GET[‘mod’]; ?> – это уязвимость, зовется PHP инъекцией – всегда проверяйте входные данные.
  6. Реализация авторизации, когда строка запроса к БД содержит что-то типа: “SELECT * FROM users WHERE login=”.$_GET[‘login’].” AND password=”.$_GET[‘password’] – читаем что такое SQL инъекции (это относится ко всем входным данным и SQL запросам)
  7. Доверяем POST переменным больше чем GET? – Будьте уверены подделать так же легко, так что проверяем
  8. AJAX это хорошо, дырявый AJAX – плохо – не забывай проверять права доступа для функций вызываемых в AJAX’е
  9. Не проверяем залитые пользователями файлы – теперь и мы знаем что такое PHP Backdoor
  10. Присваивание в условии: <?php if ($auth = true) { … } ?> такую ошибку Вы будете долго искать – старайтесь избегать подобных конструкций
  11. Переписываем функции PHP – воспользуйтесь поиском по manual’y
  12. Выражение <?php if (array_search(‘needle’, $array) != FALSE) {…} ?> – не сработает если искомый элемент доступен по ключу 0 либо “” – читаем внимательней manual по каждой функции
  13. Работаем под windows, хостинг на linux и сайт не поднимается – имя файла index.php, Index.php, INDEX.php и т.д. это все разные файлы для linux систем, а мы про это забыли
  14. Отключение вывода ошибок – код должен быть чистым  – так что error_reporting(E_ALL) Вам в помощь, следите даже за Notic’ами
  15. Залив проект на хостинг – отключите вывод ошибок на экран – логируйте их в файл – не будете краснеть потом
  16. PHP файлы имеют не стандартное расширение (к примеру .inc вместо .php) – если не защитить такие файлы, то может быть очень стыдно
  17. В рассчете на short_tags, поленились писать польностью <?php … ?> – теперь на новом хостинге переписываем шаблоны
  18. Понадеялись на magic_quotes, теперь наше приложение надежно как швейцарский … сыр
  19. Соблюдай стандарты кодирования и пиши комментарии к коду – твои последователи скажут спасибо
  20. Не пиши маты в сообщениях об ошибках и комментариях – заказчик может просматривать и тогда прийдется долго оправдываться
  21. Читают статью http://php.spb.ru/php/speed.html и думают, что только так можно/нужно оптимизировать код – это “блошиная” оптимизация относится к PHP3
  22. Реализуем функционал БД средствами PHP – array_search вместо WHERE, ну и другие чудеса незнания SQL
  23. Стучимся к БД в рекурсии – стараемся обходить подобные реализации
  24. Не нужно забивать гвозди пасатижами – “Мне надо реализовать гостевую книгу – возьму-ка я свой любимый набор библиотек на 6 мегабайт”
  25. Узнали Smarty, и теперь уверены, что научились разделять логику и отображение

Если есть что добавить – пишем в комментариях…

Понравилась статья? Поделить с друзьями:
  • Частые ошибки crc
  • Частые ошибки bpmn
  • Частые акцентологические ошибки
  • Частотный регулятор данфосс коды ошибок
  • Частотный регулятор danfoss ошибка 84