Избегайте прямого внедрения параметров в 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 или мок при тестировании – дело пары строк. Именно в этом смысл шаблона: отвязка логики от конкретной реализации хранения. Всё, что связано с хранилищем, уходит в репозиторий. Всё остальное – выше по слою.