Привет, Хабр! Очень много статей написано о PDO, но при этом очень мало реальных примеров. В этой статье я хочу представить свою версию класса для работы с базой данных (далее - БД). Эта статья будет полезна начинающим программистам, которые только осваивают эту технологию.

Внимание! Моё мнение может отличаться от вашего, поэтому хочу сразу сказать, что эта статья не есть истина в последней инстанции и реализация этого класса зависит от программиста и его предпочтений.

Вступление

Начнём с класса DB.

<?php 

	// use PDO - нужно только в том случае, если вы 
	// Используете namespace выше. Далее по коду он будет
	// Но если он вам не нужен - спокойно удаляйте
	use PDO;

	class DB
	{

		public function __construct()
		{
			
		}

	}

?>

Тут, я думаю, всё понятно, эти вещи можно не объяснять.

Далее большая часть пояснений будет содержаться в коде.

<?php 

	use PDO;

	class DB
	{
		// Переменная, хранящая объект PDO
		private $db;

		public function __construct()
		{
			// Файл dbinfo.php возвращает массив для 
			// Подключения к БД
			$dbinfo = require 'path/to/dbinfo.php';
			// Подключение
			$this->db = new PDO('mysql:host=' . $dbinfo['host'] . ';dbname=' . $dbinfo['dbname'], $dbinfo['login'], $dbinfo['password']);
		}

	}

?>

Прекрасно, мы подключились к БД. Теперь нам нужно создать метод, который позволит совершать SQL запросы.

<?php 

	use PDO;

	class DB
	{
		// Объект класса PDO
		private $db;

		// Соединение с БД
		public function __construct()
		{
			$dbinfo = require 'path/to/dbinfo.php';
			$this->db = new PDO('mysql:host=' . $dbinfo['host'] . ';dbname=' . $dbinfo['dbname'], $dbinfo['login'], $dbinfo['password']);
		}

		// Операции над БД
		public function query($sql, $params = [])
		{
			
		}

	}

?>

Реализация метода query

Мы готовы к реализации этого метода, но у нас возникает вопрос:

"Что за параметры он принимает и как он должен их использовать?"

Ответ на первый вопрос очевиден:

  • $sql - переменная с текстом SQL запроса.

  • $params - переменная с какими-то параметрами для запроса.

А что со вторым вопросом?

Всё так же просто, что бы ответить на этот вопрос, мы должны узнать, в каком виде нам подают эти параметры. А получаем мы их вот такими:

<?php 

	$sql = "SELECT * FROM `table` WHERE id = :id";
	$params = [
		'id' => 5
	];

?>

У незнающего человека возникает вопрос: "Что за двоеточие?" Я тут же отвечаю - такие запросы называются подготовленными и используются, дабы исключить возможность SQL инъекции.

Вернёмся к предыдущему вопросу и ответим на него:

Мы должны обойти массив $params и подставить значение в запрос.

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

<?php 

	use PDO;

	class DB
	{
		// Объект класса PDO
		private $db;

		// Соединение с БД
		public function __construct()
		{
			$dbinfo = require 'path/to/dbinfo.php';
			$this->db = new PDO('mysql:host=' . $dbinfo['host'] . ';dbname=' . $dbinfo['dbname'], $dbinfo['login'], $dbinfo['password']);
		}

		// Операции над БД
		public function query($sql, $params = [])
		{
			// Подготовка запроса
			$stmt = $this->db->prepare($sql);
			
			// Обход массива с параметрами 
			// и подставление значений
			if ( !empty($params) ) {
				foreach ($params as $key => $value) {
					$stmt->bindValue(":$key", $value);
				}
			}
			
			// Выполняем запрос
			$stmt->execute();
			// Возвращаем ответ
			return $stmt->fetchAll(PDO::FETCH_ASSOC);
		}

	}

?>

Некоторые комментарии немного искажают истину, но при этом кардинально ничего не меняют

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

  • getAll() - двумерный массив, индексированный числами по порядку

  • getRow() - одномерный массив, первую строку результата

Моя реализация

Эти методы могут быть реализованы по разному, но я представлю свою реализацию.

<?php 

	use PDO;

	class DB
	{
		// Объект класса PDO
		private $db;

		// Соединение с БД
		public function __construct()
		{
			$dbinfo = require 'path/to/dbinfo.php';
			$this->db = new PDO('mysql:host=' . $dbinfo['host'] . ';dbname=' . $dbinfo['dbname'], $dbinfo['login'], $dbinfo['password']);
		}

		// Операции над БД
		public function query($sql, $params = [])
		{
			// Подготовка запроса
			$stmt = $this->db->prepare($sql);
			
			// Обход массива с параметрами 
			// и подставляем значения
			if ( !empty($params) ) {
				foreach ($params as $key => $value) {
					$stmt->bindValue(":$key", $value);
				}
			}
			
			// Выполняя запрос
			$stmt->execute();
			// Возвращаем ответ
			return $stmt->fetchAll(PDO::FETCH_ASSOC);
		}

		public function getAll($table, $sql = '', $params = [])
		{
			return $this->query("SELECT * FROM $table" . $sql, $params);
		}

		public function getRow($table, $sql = '', $params = [])
		{
			$result = $this->query("SELECT * FROM $table" . $sql, $params);
			return $result[0]; 
		}

	}

?>

Здесь эти методы реализованы, возможно не лучшим образом, но реализованы.

Так же сюда можно добавить ещё функции, упрощающие жизнь, но лично мне этого с головой хватает.

Можно добавить следующие методы:

  • getOne() - возвращает первый элемент первой строки результата

  • getCol() - возвращает 1 колонку таблицы

  • и т.д.

Применение

Мы написали наш класс. Теперь нам нужно протестировать его в реальном "бою".

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

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

Так как ранее я не показал файл dbinfo.php - сейчас я приведу код этого конфигурационного файла.

<?php 

	// Не забудьте поменять данные
	return [
		'host' => '127.0.0.1',
		'dbname' => 'test',
		'login' => 'root',
		'password' => ''
	];

?>

Это понятно и не требует объяснения, идём дальше. Теперь, давайте попросим БД test вернуть нам значение всех постов при помощи нашего класса.

<?php
	
	// class DB {...} 
	// Создаём объект
	$db = new DB;

	// Получаем и выводим данные
	echo "<pre>";
	print_r($db->getAll('posts'));

?>

На выходе мы имеем следующее:

данные из БД
данные из БД

Отлично, а теперь давайте выведем только первый пост.

<?php
	
	// class DB {...} 
	// Создаём объект
	$db = new DB;

	// Получаем и выводим данные
	echo "<pre>";
	print_r($db->getRow('posts'));

?>
данные из БД
данные из БД

Прекрасно. Ну и в конце добавим запись и выведем данные до и после.

<?php
	
	// class DB {...} 
	// Создаём объект
	$db = new DB;

	// Получаем и выводим данные
	echo "<h1>До</h1><pre>";
	print_r($db->getAll('posts'));
	echo "</pre><h1>После</h1><pre>";
	$params = [
		'title' => 'Заголовок через PHP', 
		'author' => 'Автор через PHP'
	];
	$db->query('INSERT INTO `posts` ( title, author ) VALUES ( :title, :author )', $params);
	print_r($db->getAll('posts'));
?>

Результат:

данные из БД
данные из БД

Мы получили то, что хотели и убедились в том, что этот класс рабочий.

В заключении

В конце статьи я хочу повториться и сказать, что моя реализация не идеальна, но всё таки данный класс работает и выполняет свою главную функцию - работает с базой данных. Надеюсь для вас эта статья была полезной

Ссылка на github: class DB