• Блог
  • Запросы к БД через PDO

В MODX предусмотрены 2 основных способа работы с базой данных — через xPDO и через PDO. По первому способу информации достаточно много. Как правило, разработчики работают с БД именно через xPDO. Но бывают такие случаи, когда нужно использовать PDO. Например, быстро добавить какое-нибудь значение в свою таблицу или написать сложный запрос. В этом случае разработчик должен понимать, что он делает, потому что в связи между таблицами, в отличие от xPDO, не работают и могут возникнуть проблемы со ссылочной целостностью.

При работе с xPDO используются классы и схемы, в которых указаны типы данных, их связи. Поэтому, если вы хотите обратиться к пользовательским классам, вам предварительно нужно загрузить модель данных и только потом строить запросы. А в случае с PDO, вы можете написать обычный SQL-запрос и отправить его на сервер.

Основные методы PDO — xPDO::query($sql), xPDO::exec($sql) и xPDO::prepare()/PDOStatement::execute(). По ним и пробежимся.

xPDO::exec($sql)

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

// Удаляем все неактивные записи из произвольной таблицы
$sql = "DELETE FROM myTable WHERE active = 0";
$count = $modx->exec($sql);
// Получим количество удаленных записей
print("Удалено $count записей.");

Важно запомнить, что этот метод возвращает только количество строк. Т.е. оператор SELECT в данном случае используется, как если бы запрос был SELECT COUNT(*).

$sql = "SELECT * FROM modx_users WHERE sudo = 1";
$count = $modx->exec($sql);
// Получим количество администраторов с правами SUDO
print("Количество пользователей с неограниченными правами - $count.");

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

xPDO::query($sql)

Этот метод выполняет SQL запрос и возвращает результирующий набор в виде объекта PDOStatement. Опять же, данные должны быть уже подготовлены и экранированы. Иначе можно пропустить SQL инъекцию.

PDO::query() возвращает объект PDOStatement или FALSE, если запрос выполнить не удалось.

$sql = "SELECT * FROM modx_users WHERE active = 1";
// Вариант 1.
$statement = $modx->query($sql);
$users = $statement->fetchAll(PDO::FETCH_ASSOC);
foreach ($users as $user) {
    print $user['username'] .'<br/>';
}
// Вариант 2. Если не нужно зачитывать данные в массив
foreach ($modx->query($sql) as $user) {
    print $user['username'] .'<br/>';
}

Узнать количество записей, задействованных в последнем запросе, можно с помощью специального метода PDOStatement::rowCount().

$count = $statement->rowCount();

Если в запросе используются переменные или запрос планируется использовать несколько раз, то для этих случаев подойдут подготовленные выражения (prepared statements).

xPDO::prepare($sql) и PDOStatement::execute()

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

  • Не нужно подготавливать запрос каждый раз. Немного повышает производительность системы.
  • Запрос не нужно экранировать, драйвер БД может делает это автоматически — что значительно снижает риск получить SQL инъекцию.

PDO поддерживает 2 вида плейсхолдеров — позиционные (?), для которых важен порядок передаваемых переменных, и именованные (:name), для которых порядок не важен.

// 1. Позиционный плейсхолдер
$sql = 'SELECT name FROM modx_users WHERE email = ?';
// Подготавливаем шаблон SQL запроса
$statement = $modx->prepare($sql);
// Выполняем запрос подставляя данные
if ( $statement->execute(array($_GET['email'])) ) {
    $result = $statement->fetchAll(PDO::FETCH_ASSOC);
}

// 2. Именованный плейсхолдер
$sql = 'SELECT name FROM modx_users WHERE email = :email';
// Подготавливаем шаблон SQL запроса
$statement = $modx->prepare($sql);
// Выполняем запрос подставляя данные
if ( $statement->execute(array('email'=>$_GET['email'])) ) {
    $result = $statement->fetchAll(PDO::FETCH_ASSOC);
}

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

Ещё передать данные в подготовленный запрос можно используя специальные методы PDOStatement::bindParam() и PDOStatement::bindValue(). Подробнее про них можно прочитать в официальной документации.

Важно понимать, что работая с БД через PDO ответственность за корректность SQL запроса, ссылочную целостность данных и безопасность ложится целиком на программиста. Поэтому я советую использовать возможности xPDO.
0   25524

Комментарии ()

  1. Василий Столейков 11 февраля 2016 # 0
    А можно пример с PDOStatement::rowCount()?
    1. Сергей Шлоков 11 февраля 2016 # +1
      $sql = "SELECT * FROM modx_users WHERE active = 1";
      $statement = $modx->query($sql);
      print 'Количество записей - '.$statement->rowCount();
      
      1. Василий Столейков 11 февраля 2016 # 0
        Спасибо, помогло!
    2. Андрей 20 декабря 2017 # 0
      Добрый день.
      Помогите сформировать запрос такого характера:
      1. У меня есть таблица с некими записями, в которой есть поля: id (AI), vid, pid, aid
      2. Мне нужно получить следующую запрись из БД, зная о текущей все (id, vid, pid, aid), но чтобы у следующей записи последние 3 параметра (vid, pid, aid) были такие же, как у текущей.
      3. Сложность для меня в том, что это не обязательно может быть соседний id, может он вообще будет через десяток записей.
      Прошу помощи с формированием запроса))
      1. Сергей Шлоков 20 декабря 2017 # 0
        Давайте размышлять последовательно: вам нужны записи, у которых должны быть определённые параметры. Для этого нужно использовать WHERE с соответствующими равенствами.
        Далее, нужна одна из следующих по порядку записей, а не предыдущая. Т.е. у неё id должен быть больше id текущей. Значит в WHERE нужно добавить условие неравенства.
        Ну и наконец, запись нужна только одна. Поэтому нужно ограничить запрос через LIMIT. И обязательно нужно удостовериться, что это именно следующая запись, а не вторая или третья по очереди. Для чего нужно сделать соответствующую сортировку ORDER BY.
        Осталось всё это совместить.
        А следом попробовать сформировать этот запрос через xPDO.
        1. Андрей 20 декабря 2017 # 0
          Все, сделал. Спасибо за подсказку!
      2. infomos 22 октября 2018 # 0
        Добрый день!
        Может, вы в курсе, как решить эту задачу?

        Столкнулся с задачей импорта из кастомного csv данных в свою таблицу, сделал через INSERT INTO… VALUES… ON DUPLICATE KEY UPDATE. Работает, но долго, как по мне.

        Для того, чтобы достать id последней вставленной или обновленной записи, пользуюсь $modx->lastInsertId($q), все показывает правильно: если запись была, то возвращает ее id, если она новая, то id вставленной записи.

        Попробовал перейти на prepared statements до перебора цикла, а дальше при переборе уже
        $q->bindValue(1, $data&#910&#93);
        $q->bindValue(2, $data&#911&#93);

        $q->execute();

        Все, на этом id последней вставленной записи ломается: возвращает id больше, чем последний AUTO_INCREMENT в таблице.
        1. Сергей Шлоков 22 октября 2018 # 0
          Без понятия. Если обратиться к документации, то можно найти комментарий, где описывается некорректная работа lastinsertid() при использовании конструкции ON DUPLICATE KEY UPDATE. Возможно в режиме подготовленных запросов возникает такой же сбой.
          1. infomos 22 октября 2018 # 0
            Вот так и выходит, что пользуется весь мир, а баг (или фича?) продолжает существовать ) И на том спасибо!
          2. Nikker123 18 марта 2022 # 0
            Привет, по MODX, стукни пожалуйста на телегу w_mustang
          3. inetlover 09 февраля 2019 # 0
            Сергей, здравствуйте!
            Подскажите, пожалуйста, как перевести на PDO, вот такой запрос:
            SELECT id FROM modx_ha_user_services WHERE identifier = '72340033' AND  provider = 'Vkontakte';
            Нужно получить id из таблицы которую создает компонент HybridAuth.
            1. Сергей Шлоков 09 февраля 2019 # +1
              Добрый день!
              А в чём сложность? Выбирайте любой вариант из статьи.
              1. inetlover 09 февраля 2019 # 0
                Немного тормознул, почитал документацию про PDOStatement и сделал, как мне нужно. Заменил fetchAll() на fetchColumn(), так как мне не нужен массив.
                $sql = "SELECT id FROM modx_ha_user_services WHERE identifier = '72340033' AND  provider = 'Vkontakte';";
                $statement = $modx->query($sql);
                $users = $statement->fetchColumn();

            2. Adam 15 мая 2019 # 0
              Здравствуйте, Сергей.
              Можно ли подготовить запрос типа:
              $sql = "SELECT * FROM profile= :рrofile";   // буква 'p' на кирильском, смайл выскакивал

              Делал функцию, которая выводит инфу с нужно мне таблицы. До этого плейсхолдеры работали предано. Но сейчас почему то вылезает ошибка, о неправильном синтаксисе sql-запроса. Есть подозрения, что для FROM тега плейсходеры не работают. Так как до этого их для WHERE или VALUE и тп., но для такого у меня не было, да и во всех статьях тоже его не трогают
              Может ли такое быть? Может уже придется меня подключение PDO, не на прямую в нужно БД, а просто в сам MySQL.
              За раннее спасибо.
              1. Сергей Шлоков 15 мая 2019 # 0
                Добрый день!
                Я не понимаю, что Вы пытаетесь сделать. Какой запрос у Вас должен получиться в чистом виде? Ваш запрос работать не будет. Он составлен не правильно. В секции FROM нужно указать таблицу. Иначе как MySql будет подготавливать запрос, если он не знает к какой таблице обращаться.

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

              Выделите опечатку и нажмите Ctrl + Enter, чтобы отправить сообщение об ошибке.