Топ ошибок начинающих PHP разработчиков: продвинутый уровень
Когда вы переходите от простых скриптов к сложным веб-приложениям на PHP, количество подводных камней резко возрастает. Многие разработчики, освоив базовый синтаксис, совершают системные ошибки, которые приводят к утечкам памяти, уязвимостям и падению производительности. В этой статье мы разберём 4 продвинутые ошибки, которые часто встречаются у разработчиков среднего уровня, и покажем, как их исправить.
1. Неправильное управление сессиями и утечки памяти
Одна из самых коварных ошибок — это неправильная работа с сессиями. Многие разработчики хранят в сессиях большие объёмы данных (например, результаты выборок из БД или сериализованные объекты). Это не только замедляет работу, но и может привести к утечкам памяти на сервере.
Проблема: Сессия блокируется на время выполнения скрипта. Если вы храните в ней 10 МБ данных, каждый запрос будет читать и записывать этот объём, а при параллельных запросах — выстраиваться в очередь.
Пример плохого кода:
<?php
session_start();
// Загружаем в сессию массив из 10000 записей
$_SESSION['big_data'] = $db->query("SELECT * FROM logs")->fetchAll();
?>
Решение: Используйте сессии только для идентификации (user_id, токен). Все остальные данные храните в кэше (Redis, Memcached) или в базе данных.
<?php
session_start();
$_SESSION['user_id'] = $user->id;
// Данные получаем по требованию
$cache = new RedisCache();
$data = $cache->get('user_data_' . $user->id);
if (!$data) {
$data = $db->query("SELECT * FROM logs WHERE user_id = ?", [$user->id])->fetchAll();
$cache->set('user_data_' . $user->id, $data, 3600);
}
?>
2. Неправильное кэширование и игнорирование инвалидации
Вторая по популярности ошибка — это кэширование "навсегда". Разработчики ставят время жизни кэша (TTL) в 24 часа или больше, а потом удивляются, почему пользователи видят устаревшие данные. Ещё хуже — кэширование без учёта изменений в базе данных.
Проблема: Если вы кэшируете список товаров на 1 час, а администратор изменил цену — пользователи увидят старую цену ещё 60 минут.
Пример плохого кода:
<?php
$cacheKey = 'product_list';
$products = $cache->get($cacheKey);
if (!$products) {
$products = $db->query("SELECT * FROM products")->fetchAll();
$cache->set($cacheKey, $products, 86400); // 24 часа
}
?>
Решение: Используйте тегирование кэша или принудительную инвалидацию при изменении данных.
<?php
// При обновлении товара
function updateProduct($id, $data) {
$db->update("products", $data, ["id" => $id]);
// Инвалидируем кэш
$cache->delete('product_list');
$cache->delete('product_' . $id);
}
// При чтении
$products = $cache->remember('product_list', 3600, function() use ($db) {
return $db->query("SELECT * FROM products WHERE active = 1")->fetchAll();
});
?>
3. SQL-инъекции через "безопасные" функции
Многие разработчики считают, что если они используют mysqli_real_escape_string() или addslashes(), то они защищены от SQL-инъекций. Это опасное заблуждение. Эти функции не защищают от всех типов атак, особенно при работе с LIKE, ORDER BY и LIMIT.
Проблема: Функция mysqli_real_escape_string() не экранирует символы для оператора LIKE (например, % и _). Если вы подставляете пользовательский ввод в LIKE, злоумышленник может получить доступ к данным, которые не должен видеть.
Пример уязвимости:
<?php
$search = $_GET['q'];
$safe = mysqli_real_escape_string($conn, $search);
$query = "SELECT * FROM users WHERE username LIKE '%$safe%'";
// Если $search = "%", то будут выбраны все пользователи
?>
Решение: Используйте только подготовленные запросы (PDO или MySQLi с параметрами). Для LIKE дополнительно экранируйте символы % и _.
<?php
$search = $_GET['q'];
// Экранируем спецсимволы LIKE
$search = str_replace(['%', '_'], ['\%', '\_'], $search);
$stmt = $pdo->prepare("SELECT * FROM users WHERE username LIKE ?");
$stmt->execute(['%' . $search . '%']);
?>
4. Игнорирование типов данных и строгих сравнений
PHP — язык с динамической типизацией, и это часто приводит к неожиданным ошибкам. Например, сравнение строки с числом, или использование == вместо === может дать совершенно не тот результат, который вы ожидаете.
Проблема: Функция in_array() по умолчанию использует нестрогое сравнение. Если вы проверяете, есть ли ID в массиве, то значение "0" может совпасть с пустой строкой.
Пример ошибки:
<?php
$allowedIds = [1, 2, 3];
$inputId = "0"; // Пришло из формы
if (in_array($inputId, $allowedIds)) {
// Это условие выполнится! Потому что "0" == 0, а 0 == false
echo "Доступ разрешён";
}
?>
Решение: Всегда используйте строгие сравнения и явно указывайте третий параметр true в in_array().
<?php
$allowedIds = [1, 2, 3];
$inputId = (int)$_POST['id']; // Явно приводим к int
if (in_array($inputId, $allowedIds, true)) {
echo "Д