Статьи

Повторное внедрение PDO — правильный способ доступа к базам данных в PHP

PDO является аббревиатурой от PHP Data Objects. Как следует из названия, это расширение дает вам возможность взаимодействовать с вашей базой данных через объекты.

Stock graphic of database icon branching into other icons

Почему не mysql и mysql ?

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

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

PDO является объектно-ориентированным

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

В случае PHP лучше PHP означает объектно-ориентированный PHP . Это означает, что чем больше вы будете использовать объекты, тем лучше вы сможете протестировать свой код, написать повторно используемые компоненты и, как правило, увеличить свою зарплату.

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

абстракция

Представьте, что вы написали приложение-убийцу, используя MySQL, на своем текущем рабочем месте. Вдруг кто-то в цепочке решает, что вы должны перенести свое приложение на использование Postgres . Чем ты планируешь заняться?

Вы должны сделать много грязных замен , таких как преобразование mysql_connect или mysqli_connect в pg_connect , не говоря уже о всех других функциях, которые вы использовали для выполнения запросов и получения результатов. Если бы вы использовали PDO, это было бы очень просто. Нужно изменить только несколько параметров в основном файле конфигурации, и все готово.

Это позволяет привязку параметров

Привязка параметров — это функция, которая позволяет заменять заполнители в вашем запросе значением переменной. Это означает:

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

Вы можете извлекать данные в объекты

Люди, которые использовали ORM, такие как Doctrine, знают, как важно представлять данные в ваших таблицах объектами. Если вы хотите иметь эту функцию, но не хотите изучать ORM или не хотите интегрировать ее в уже существующее приложение, PDO позволит вам извлечь данные из вашей таблицы в объект.

Расширение mysql больше не поддерживается!

Да, расширение mysql окончательно удалено из PHP 7 . Это означает, что если вы собираетесь использовать PHP 7, вам нужно изменить все эти функции на mysqli_* вместо mysql_* . Это прекрасное время, чтобы просто перейти прямо на PDO, потому что это помогает вам в написании поддерживаемого, переносимого кода с гораздо меньшими усилиями.

Я надеюсь, что приведенные выше причины убедили вас начать интеграцию PDO в ваше приложение. Не беспокойтесь о настройке; у вас уже может быть это в вашей системе!

Проверка существования PDO

Если вы используете PHP 5.5.X и выше, скорее всего, ваша установка уже включает PDO. Для проверки просто откройте терминал в Linux и Mac OS X или командную строку в Windows и введите следующую команду:

 php - i |  grep 'pdo' 

Вы также можете создать php-файл под webroot и вставить в phpinfo() оператор phpinfo() :

 <? php phpinfo (); 

Теперь откройте эту страницу в вашем браузере и pdo строку pdo .

Если у вас есть PDO и MySQL, пропустите инструкции по установке. Если у вас есть PDO, но нет его для MySQL, вам просто нужно установить расширение mysqlnd согласно приведенным ниже инструкциям. Однако, если у вас вообще нет PDO, ваш путь будет длиннее, но не сложнее! Продолжайте читать, и мы расскажем вам, как подготовиться, установив PDO и mysqlnd !

Установка ПДО

Если вы уже установили PHP из репозитория через менеджер пакетов (например, apt, yum, pacman и т. Д.), Установка PDO очень проста и понятна; просто запустите команду установки, которая указана под вашей операционной системой и дистрибутивом ниже. Если вы этого не сделали, я также включил мои рекомендуемые методы для начала с нуля.

Fedora, RedHat и CentOS

Во-первых, если у вас его еще нет, добавьте репозиторий Remi, используя инструкции, приведенные в их блоге . Когда это будет сделано, вы можете легко установить php-pdo с помощью следующей команды:

 sudo yum -- enablerepo = remi , remi - php56 install php - pdo 

Примечание . Хотя remi репозитория remi необходимо, вам нужно заменить remi-php56 на нужный вам репозиторий в приведенной выше команде.

Конечно, если у вас его еще нет, вам также нужно установить расширение mysqlnd с помощью следующей команды:

 sudo yum -- enablerepo = remi , remi - php56 install php - mysqlnd 

Debian и Ubuntu

В Ubuntu вам нужно добавить репозиторий Ondrej . Эта ссылка указывает на PPA для 5.6, но вы также можете найти ссылки на предыдущие версии.

В Debian вы должны добавить репозиторий Dotdeb в вашу систему.

В обоих этих дистрибутивах после установки метапакета php5 у вас уже есть готовый и настроенный PDO. Все, что вам нужно сделать, это просто установить расширение mysqlnd :

 sudo apt - get  install php5 - mysqlnd 

Windows

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

В Windows вы обычно получаете полный набор ламп, используя Wamp или Xampp . Вы также можете просто скачать PHP прямо с windows.php.net . Очевидно, что если вы сделаете последнее, у вас будет только PHP, а не весь стек.

В любом случае, если PDO еще не активирован, вам просто нужно раскомментировать его в своем php.ini . Используйте средства, предусмотренные в вашем стеке ламп, для редактирования php.ini или, в случае загрузки PHP с windows.php.net , просто откройте папку, которую вы выбрали в качестве установочного каталога, и отредактируйте php.ini . Как только вы это сделаете, раскомментируйте эту строку:

 ; extension = php_pdo_mysql . dll 

Начиная с PDO: обзор высокого уровня

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

  • Подключение к вашей базе данных
  • При желании, подготовка заявления и параметры привязки
  • Выполнение запроса

Подключение к вашей базе данных

Чтобы подключиться к вашей базе данных, вам нужно создать новый объект PDO и передать ему имя источника данных, также известное как DSN.

Как правило, DSN состоит из имени драйвера PDO, за которым следует двоеточие, за которым следует специфический для драйвера PDO синтаксис соединения. Дополнительную информацию можно найти в документации по драйверу PDO .

Например, вот как вы можете подключиться к базе данных MySQL:

 $connection =   new  PDO ( 'mysql:host=localhost;dbname=mydb;charset=utf8' ,   'root' ,   'root' ); 

Функция выше содержит DSN, имя пользователя и пароль. Как указано выше, DSN содержит имя драйвера ( mysql ) и параметры драйвера. Для mysql такими параметрами являются host (в формате ip:port ), dbname и charset .

Запросы

Вопреки тому, как mysql_query() и mysqli_query() , в PDO есть два вида запросов: те, которые возвращают результат (например, select и show ), и те, которые не (например, insert , delete и т. Д.). Давайте сначала посмотрим на более простой вариант: те, которые этого не делают.

Выполнение запросов

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

 $connection -> exec ( 'INSERT INTO users VALUES (1, "somevalue"' ); 

Технически я солгал, когда сказал, что эти запросы не возвращают результат. Если вы измените приведенный выше код на следующий код, вы увидите, что exec() возвращает количество затронутых строк:

 $affectedRows =  $connection -> exec ( 'INSERT INTO users VALUES (1, "somevalue"' ); echo $affectedRows ; 

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

Получение результатов запроса

С помощью mysql_query() или mysqli_query() вы можете выполнить запрос следующим образом:

 $result =  mysql_query ( 'SELECT * FROM users' ); 

 while ( $row =  mysql_fetch_assoc ( $result ))   { echo $row [ 'id' ]   .   ' '   .  $row [ 'name' ]; 
 } 

Однако с PDO все становится намного более интуитивно понятным:

 foreach ( $connection -> query ( 'SELECT * FROM users' )   as  $row )   { echo $row [ 'id' ]   .   ' '   .  $row [ 'name' ]; 
 } 

Режимы выборки: Assoc , Num , Obj и class

Как и в случае расширений mysql и mysqli , вы можете получать результаты различными способами в PDO . Для этого вы должны передать одну из констант PDO::fetch_* на странице справки для функции fetch . Если вы хотите получить все свои результаты одновременно, вы можете использовать функцию fetchAll .

Ниже приведены некоторые из наиболее полезных режимов выборки.

  • PDO :: FETCH_ASSOC: возвращает массив, проиндексированный по имени столбца. То есть в нашем предыдущем примере вам нужно использовать $row['id'] чтобы получить id .
  • PDO :: FETCH_NUM: возвращает массив, проиндексированный по номеру столбца. В нашем предыдущем примере мы получили столбец id с помощью $row[0] потому что это первый столбец.
  • PDO :: FETCH_OBJ: возвращает анонимный объект с именами свойств, которые соответствуют именам столбцов, возвращаемых в вашем наборе результатов. Например, $row->id будет содержать значение столбца id .
  • PDO :: FETCH_CLASS: возвращает новый экземпляр запрошенного класса, сопоставляя столбцы результирующего набора с именованными свойствами в классе. Если fetch_style включает PDO :: FETCH_CLASSTYPE (например, PDO :: FETCH_CLASS | PDO :: FETCH_CLASSTYPE), тогда имя класса определяется по значению первого столбца. Если вы помните, мы отметили, что PDO в своей самой простой форме может отображать имена столбцов в определяемые вами классы. Эта константа — то, что вы бы использовали для этого.

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

В качестве примера давайте получим наши строки в виде ассоциативных массивов:

 $statement =  $connection -> query ( 'SELECT * FROM users' ); 

 while ( $row =  $statement -> fetch ( PDO :: FETCH_ASSOC ))   { echo $row [ 'id' ]   .   ' '   .  $row [ 'name' ]; 
 } 

Примечание . Мы рекомендуем всегда выбирать режим выборки, поскольку выборка результатов в виде PDO::FETCH_BOTH (по умолчанию) занимает в два раза больше памяти, поскольку PHP обеспечивает доступ к различным значениям столбцов как через ассоциативный массив, так и обычный массив.

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

 class   User 
 { 

     protected  $id ; 
     protected  $name ; 

     public   function  getId () 
     { 
         return  $this -> id ; 
     } 

     public   function  setId ( $id ) 
     { $this -> id =  $id ; 
     } 

     public   function  getName () 
     { 
         return  $this -> name ; 
     } 

     public   function  setName ( $name ) 
     { $this -> name =  $name ; 
     } 

 } 

Теперь мы можем сделать тот же запрос снова, на этот раз, используя наш класс User , который в этих случаях также известен как Model , Entity или обычный старый объект PHP (взят из Plain Old Java Object в мире Java). ,

 $statement = $connection->query('SELECT * FROM users'); while($row = $statement->fetch(PDO::FETCH_CLASS, 'User')) { echo $row->getId() . ' ' . $row->getName(); } 

Подготовленные заявления и параметры привязки

Чтобы понять привязку параметров и их преимущества, мы должны сначала глубже изучить, как работает PDO. Когда мы вызвали $statement->query() выше, PDO внутренне подготовил оператор и выполнил его, вернув полученный нам оператор.

Когда вы вызываете $connection->prepare() , вы создаете подготовленный оператор . Подготовленные операторы — это функция некоторых систем управления базами данных, которая позволяет им получать запрос, подобный шаблону, компилировать его и выполнять, когда они получают значение заполнителей — воспринимайте их как рендеринг ваших шаблонов Blade или Twig .

Когда вы позже вызовете $statement->execute() , вы передаете значения для этих заполнителей и сообщаете системе управления базами данных, что нужно выполнить запрос. Это похоже на вызов функции render() вашего движка шаблонов.

Чтобы увидеть это в действии, давайте создадим запрос, который возвращает указанный id из базы данных:

 $statement =  $connection -> prepare ( 'Select * From users Where id = :id' ); 

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

 $id =   5 ; $statement -> execute ([ 
     ':id'   =>  $id ]); 

Затем вы можете получить результат из оператора:

 $results =  $statement -> fetchAll ( PDO :: FETCH_OBJ ); 

Преимущества привязки параметров

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

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

 $results =  mysql_query ( sprintf ( "SELECT * FROM users WHERE name='%s'" , mysql_real_escape_string ( $name ) 
     ) 
 )   or   die ( mysql_error ()); 

Вместо этого вы можете сказать:

 $statement =  $connection -> prepare ( 'Select * FROM users WHERE name = :name' ); $results =  $connection -> execute ([ 
     ':name'   =>  $name ]); 

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

 $statement =  $connection -> prepare ( 'SELECT * FROM users WHERE name = ?' ); $results =  $connection -> execute ([ $name ]); 

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

 $numberOfUsers =  $connection -> query ( 'SELECT COUNT(*) FROM users' )-> fetchColumn (); $users =   []; $statement =  $connection -> prepare ( 'SELECT * FROM users WHERE id = ? LIMIT 1' ); 

 for   ( $i =   1 ;  $i <=   5 ;  $i ++)   { $id =  rand ( 1 ,  $numberOfUsers ); $users []   =  $statement -> execute ([ $id ])-> fetch ( PDO :: FETCH_OBJ ); 
 } 

Когда мы в первый раз вызываем функцию prepare , мы говорим нашей СУБД анализировать, компилировать и кэшировать наш запрос. Позже в нашем цикле for мы только отправляем ему значения для заполнителя — больше ничего. Это позволяет быстрее выполнять запрос и возвращать его, эффективно сокращая время, необходимое нашему приложению для извлечения результатов из базы данных.

Вы также, возможно, заметили, что я использовал новую функцию в приведенном выше фрагменте кода: fetchColumn . Как вы, вероятно, можете догадаться, он возвращает значение только одного столбца и хорош для получения скалярных значений из результата запроса, такого как count , sum , min , max и другие функции, которые возвращают только один столбец в качестве результата.

Связывание значений с предложением IN

Что-то, что многих людей озадачило, когда они впервые начинают изучать PDO — это предложение IN . Например, представьте, что мы позволяем пользователю вводить разделенный запятыми список имен, которые мы храним в $names . Итак, наш код:

 $names =  explode ( ',' ,  $names ); 

На этом этапе большинство людей делают следующее:

 $statement =  $connection -> prepare ( 'SELECT * FROM users WHERE name IN (:names)' ); $statement -> execute ([ ':names'   =>  $names ]); 

Это не работает — вы можете передать только скалярное значение (например, целое число, строку и т. Д.) Подготовленным операторам! Способ сделать это — как вы уже догадались — построить строку самостоятельно.

 $names =  explode ( ',' ,  $names ); $placeholder =  implode ( ',' ,  array_fill ( 0 ,  count ( $names ),   '?' )); $statement =  $connection -> prepare ( "SELECT * FROM users WHERE name IN ($placeholder)" ); $statement -> execute ([ $names ]); 

Несмотря на свой страшный внешний вид, строка 2 просто создает массив вопросительных знаков, который содержит столько же элементов, сколько и наш массив names . Затем он объединяет элементы внутри этого массива и помещает между ними a — эффективно создавая что-то вроде ?,?,?,? , Поскольку наш массив names также является массивом, передача его в execute() работает, как и ожидалось — первый элемент связан с первым знаком вопроса, второй — со вторым знаком вопроса и так далее.

Предоставление типов данных при привязке параметров

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

  • Удобочитаемость: для того, кто читает ваш код, легко увидеть, какой тип должна иметь переменная для привязки к параметру.
  • Поддержка: знание того, что первым заполнителем в вашем запросе должно быть целое число, позволяет вам обнаруживать любые ошибки, которые выпадают. Например, если кто-то передает переменную, содержащую test в вашу функцию, которая затем будет использовать это значение для поиска определенного идентификатора в виде целого числа, наличие типа данных позволяет быстро найти ошибку.
  • Скорость: когда вы указываете тип данных переменной, вы говорите своей системе управления базами данных, что нет необходимости приводить переменную и что вы предоставляете ей правильный тип. Таким образом, у вас нет (небольших) накладных расходов, связанных с преобразованием типов данных.

Чтобы указать тип каждой переменной, я лично рекомендую функцию bindValue . Давайте изменим наш код выше, чтобы указать тип нашего заполнителя:

 $numberOfUsers =  $connection -> query ( 'SELECT COUNT(*) FROM users' )-> fetchColumn (); $users =   []; $statement =  $connection -> prepare ( 'SELECT * FROM users WHERE id = ? LIMIT 1' ); 

 for   ( $i =   1 ;  $i <=   5 ;  $i ++)   { $id =  rand ( 1 ,  $numberOfUsers ); $statement -> bindValue ( 1 ,  $id ,  PDO :: PARAM_INT ); $statement -> execute (); $users []   =  $statement -> fetch ( PDO :: FETCH_OBJ ); 
 } 

Как видите, единственное, что изменилось, — это наш вызов execute() : вместо того, чтобы передавать значения прямо ему, мы сначала связали его и указали, что его тип является целым числом.

Примечание : вы, вероятно, заметили, что мы указали первый параметр для bindValue() как 1 . Если бы мы использовали именованный параметр (рекомендуется), мы передали бы имя нашего параметра (например, :id ). Тем не менее, в случае использования ? в качестве заполнителя первый аргумент bindValue() — это число, указывающее, на какой вопросительный знак вы ссылаетесь. Будьте осторожны — это позиция с 1 индексом, то есть она начинается с 1 , а не с 0 !

Вывод

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

Вы внедрили PDO в свой существующий проект? С какими проблемами вы столкнулись? Вы рады, что переехали? Какие функции вам не хватает? Дайте нам знать в комментариях ниже!