Избегайте прямого внедрения параметров в SQL-запросы. Это первое, что нужно пресечь. Любая строка, переданная без подготовки, – дыра, в которую залетит инъекция. Используйте подготовленные выражения через PDO. Никаких «SELECT * FROM users WHERE login = ‘$login'» – только bindParam и чёткий тип данных.
Пропишите строгую типизацию при инициализации соединения. Например, в PDO используйте PDO::ATTR_EMULATE_PREPARES => false и PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION. Это не просто параметры – это механизм, предотвращающий тихие сбои и позволяющий мгновенно ловить ошибку по месту.
Забудьте про конструкции вроде mysql_query() – им место на кладбище устаревших решений. Вместо этого – mysqli с явной обработкой ошибок или PDO с поддержкой транзакций. Никаких «на авось», всё должно быть под контролем.
Кэширование запросов к часто используемым таблицам – не прихоть, а необходимость. Используйте Memcached или Redis для хранения готовых выборок. Особенно это критично при работе с фильтрами и сортировками в админке. Нет смысла каждый раз дергать сервер ради одинакового списка категорий.
Нормализация структуры – основа стабильности. Не нужно собирать всё в одну таблицу, лишь бы поместилось. Лучше больше связей, чем одно поле, в котором через запятую лежат ID-шники. Каждая сущность – свой контекст. Каждый контекст – отдельная таблица.
И, наконец, не экономьте на логике в промежуточном уровне. Писать SQL-запросы «вручную» внутри шаблонов – это всё равно что класть проводку поверх кафеля. Модель должна быть отдельной, шаблон – отдельным. Никаких компромиссов в этом вопросе быть не должно.
Подключение к базе данных через PDO с обработкой ошибок и конфигурацией
$dsn = 'mysql:host=localhost;dbname=example;charset=utf8mb4';
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // выбрасываем исключения
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // ассоциативный массив
PDO::ATTR_EMULATE_PREPARES => false, // только нативные prepared statements
];
$pdo = new PDO($dsn, 'username', 'password', $options);
Обработка ошибок – никаких `try-catch` без смысла
Ловить исключения – не самоцель. Если используешь PDO с `ERRMODE_EXCEPTION`, то контролируй точки отказа логично: в местах, где сбой критичен. Не лови ошибку просто чтобы вывести «что-то пошло не так». Логируй, проверяй тип ошибки по коду, реагируй осознанно.
Пример точечной обработки:
try {
$pdo->query("SELECT * FROM users");
} catch (PDOException $e) {
if (str_contains($e->getMessage(), 'Access denied')) {
// логика при неверных правах
} else {
throw $e;
}
}
Конфигурация – не в коде!
Не храни логины и пароли в скриптах. Используй `.env`, `config.php`, переменные окружения. Пример загрузки из `.env` через `vlucas/phpdotenv`:
require_once 'vendor/autoload.php';
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
$dotenv->load();
$pdo = new PDO(
$_ENV['DB_DSN'],
$_ENV['DB_USER'],
$_ENV['DB_PASS'],
$options
);
Таким образом, чувствительные данные не попадут в git и не будут лежать на сервере в открытом виде. Никогда не пренебрегай этим.
Актуальная справка по `PDO::ATTR_ERRMODE` и другим параметрам – https://www.php.net/manual/ru/pdo.setattribute.php.
Безопасное выполнение SQL-запросов с использованием подготовленных выражений
Вот типичный пример:
$stmt = $pdo->prepare("SELECT * FROM users WHERE email = :email");
$stmt->bindValue(':email', $userEmail, PDO::PARAM_STR);
$stmt->execute();
Не просто удобно – безопасно. Здесь переменная $userEmail
никак не влияет на структуру запроса. Она не может «сломать» SQL, даже если злоумышленник попытается передать `’; DROP TABLE users;—`.
Не злоупотребляй доверчивостью
Подготовленные выражения работают корректно только с параметрами значений. Нельзя подставлять имена таблиц, столбцов или операторов – это не защитит. Если нужно динамически строить такие части, используй whitelisting. Например, список разрешённых столбцов – и никакой свободы для пользователя вводить произвольное имя.
Одна подготовка – много вызовов
Если в цикле – не пересоздавай запрос каждый раз. Подготовь один раз, а потом просто меняй значения через bindParam()
. Это экономит ресурсы и снижает нагрузку. Например:
$stmt = $pdo->prepare("INSERT INTO logs (user_id, action) VALUES (:id, :action)");
foreach ($logData as $entry) {
$stmt->bindParam(':id', $entry['id'], PDO::PARAM_INT);
$stmt->bindParam(':action', $entry['action'], PDO::PARAM_STR);
$stmt->execute();
}
Без всяких «магий». Только конкретные шаги. Безопасно, понятно, быстро.
Актуальная справка: php.net/manual/ru/pdo.prepare.php
Организация логики доступа к данным с применением шаблона Repository
Сразу отделяйте SQL от бизнес-логики. Для этого создайте интерфейс, который описывает базовые действия: получить один элемент, список, добавить, изменить, удалить. Пусть, например, `UserRepositoryInterface` определяет методы вроде `findById(int $id): ?User` и `save(User $user): void`.
Реализацию интерфейса (`UserRepository`) связывайте с конкретной СУБД через PDO или ORM. Не загромождайте репозиторий лишней логикой – его задача исключительно передавать сущности между хранилищем и приложением. Любая фильтрация или валидация – вне этого уровня.
Пример: структура классов
Создайте простую модель `User`, затем репозиторий, который реализует интерфейс и использует подготовленные выражения:
interface UserRepositoryInterface {
public function findByEmail(string $email): ?User;
public function save(User $user): void;
}
class UserRepository implements UserRepositoryInterface {
private PDO $pdo;
public function __construct(PDO $pdo) {
$this->pdo = $pdo;
}
public function findByEmail(string $email): ?User {
$stmt = $this->pdo->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
$stmt->execute(['email' => $email]);
$data = $stmt->fetch();
return $data ? new User($data['id'], $data['email']) : null;
}
public function save(User $user): void {
$stmt = $this->pdo->prepare(
'INSERT INTO users (email) VALUES (:email) ON DUPLICATE KEY UPDATE email = :email'
);
$stmt->execute(['email' => $user->getEmail()]);
}
}
Контейнер и зависимость
Внедряйте интерфейс, а не реализацию. Контейнер зависимостей типа Symfony DI или PHP-DI может автоматически передавать нужный репозиторий в сервис. Это снижает связанность, упрощает тестирование, повышает читаемость.
Заменить MySQL на Redis, Mongo или мок при тестировании – дело пары строк. Именно в этом смысл шаблона: отвязка логики от конкретной реализации хранения. Всё, что связано с хранилищем, уходит в репозиторий. Всё остальное – выше по слою.