Здравствуйте.
Читатель моего блока наверное недоумевает – блог называется “Философия программирования”, но вот философии в нем что-то не наблюдается. Я хотел написать пост о программистах странных, но позже напишу. Сейчас хочу представить класс-обёртку для PHP/PDO.
Класс-обёртка для PHP/PDO
Если вы давно читаете мой блог (ну или понятное дело нагуглили пару постов в нем) вы знаете что я поклонник минимализма и простоты в программировании. Я пытаюсь упростить везде, где только возможно.
А как же русская народная пословица (мудрость?) “Простота хуже воровства”, спросите вы и поставите меня в тупик.
Когда я думал об этой пословице, я долго бил себя кулаком в грудь, и пытался сам себе доказать, что пословица неверна, что я прав, и лучше всё упрощать, чем усложнять. Но червь сомнения всё равно селился в моей душе и грыз меня изнутри, доводя до слёз.
Но время лечит (хоть и не всегда), и, наверное, это пословица действительно неверна, а скорее всего применима в узком спектре существования (в частных случаях), когда простота действительно раздражает.
Но в общем случае, в большинстве случаях и объективно говоря лучше всё упрощать, чем усложнять, согласитесь? Обычно усложняет тот, кто не может сделать проще или пытается скрыть какой-то обман.
Что касаемо кода (программирования), то чем проще и понятней код, чем его меньше, тем проще его поддерживать и отлаживать, верно? Тут нет никаких сомнений или исключений.
С другой стороны мы понимаем, что вряд ли возможно сократить/упростить какое-нибудь хитроумное уравнение из ядерной/квантовой физики или высшей математики (хотя кто его знает, может и можно), или молекулы ДНК, состоящие из миллионов нуклеотидов (хотя, наверное всё же можно), а также их кодирование в вычисляемый машинами код (на каком-либо языке программирования).
Хотя изумительно сложные по виду и разнообразные фракталы (например, классический фрактал Мандельброта), как вы знаете, рисуются буквально десятком (или менее) строчек кода, надо просто знать соотв. уравнения.
ОК, может хватить философии и займёмся делом?
Класс-обёртка для PHP/PDO – теперь уже точно! ))
Зачем нужны обертки, спросит некий наивный программист (новичок?). Грамотные обертки могут значительно упростить и классифицировать создания и использование узконаправленного кода.
Недостатки оберток – необходимо запомнить его функциональность (переменные, вызываемые методы и т.п.), а также вычисление машиной (процессором) лишнего кода, так как любая обертка в итоге обращается к своему родителю (то, что она оборачивает).
Преимущества оберток – значительное упрощение вычислений и кода.
Например, случай PHP PDO.
Да, в PDO есть все методы для работы с базами данных, это универсальная обертка над различными драйверами БД. В нем есть такие методы, как PDO::query, PDO::prepare, PDO::exec и т.д.
Также надо знать и учитывать класс PDOStatement, который позволяет подготавливать и вычислять различные запросы к БД и получать из них результаты.
Общие случаи работы с PDO таков:
Создаём экземпляр класса PDO:
try
{
$db = new PDO(
"mysql:host=localhost;dbname=my_database;charset=utf8",
'username',
'password',
array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES UTF8')
);
}
catch (PDOException $e)
{
die('Сайт перегружен. Повторите попытку через 5 секунд');
}
После успешного выполнения этого куска кода у нас образуется переменная $db, с помощью которой мы можем выполнять любые операции над открытой базой данных.
Например:
$prep = $db->prepare("SELECT id, name, active FROM partners WHERE email=? AND pass=? LIMIT 1");
$prep->execute(array($email, $pass));
$ar = $prep->fetch(PDO::FETCH_ASSOC);
if(!count($ar)) {
echo 'Неверное имя или пароль';
return false;
}
// ... продолжение кода
Таким образом, используя $prep как экземпляр PDOStatement, мы получаем в $ar ассоциативный массив выборки из таблицы partners открытой БД. Теперь в $ar[‘id’], $ar[‘name’, $ar[‘active’] содержат нужные значения.
Используя же обертку, мы можем написать примерно так:
$prep = DB::execute("SELECT id, name, active FROM partners WHERE email=? AND pass=? LIMIT 1", array($email, $pass));
$ar = $prep->fetch(PDO::FETCH_ASSOC);
if(!count($ar)) {
echo 'Неверное имя или пароль';
return false;
}
// ... продолжение кода
В нашей обёртке все обращения к БД осуществляются через статический класс DB, таким образом нам не нужно следить, чтобы указатель на открытую БД всегда находился в пределах видимости функций работы с ним. Это уже большой плюс.
Кроме того, обертка как мы видим в предыдущем примере упрощает ряд вызовов, например вместо двух вызовов с созданием промежуточной переменной $prep типа PDOStatement мы в данном случае используем один.
Класс DB (static)
Итак, привожу полный пример класса DB, а после – краткое описание методов класса и примеры работы с ним.
//////////////////////////////////////////////////////////
//////////////// PDO DB wrapper /////////////////////////
////////////////////////////////////////////////////////
class DB
{
// use STATIC rendering
static private $db; // db handler
static public function init() {
try {
self::$db = new PDO(
"mysql:host=localhost;dbname=my_database;charset=utf8",
'username', 'password',
array(PDO::MYSQL_ATTR_INIT_COMMAND=>'SET NAMES utf8')
);
self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
//self::$db->exec("SET NAMES utf8"); -- не нужно если сработает PDO::MYSQL_ATTR_INIT_COMMAND?
}
catch (PDOException $e)
{
die('<h3 style="color: blue">Ошибка соединения с БД. Повторите попытку через полминуты</h3>');
}
}
static public function query($sql) {
return self::$db->query($sql);
}
static public function exec($sql) {
return self::$db->exec($sql);
}
// one column result
static public function column($sql) {
return self::$db->query($sql)->fetchColumn();
}
// intval one column result
static public function columnInt($sql) {
return intval(self::$db->query($sql)->fetchColumn());
}
static public function prepare($sql) {
return self::$db->prepare($sql);
}
static public function lastInsertId() {
return self::$db->lastInsertId();
}
// prepares and execute one SQL
static public function execute($sql, $ar) {
return self::$db->prepare($sql)->execute($ar);
}
// returns error info on db handler (not stmt handler)
static public function error() {
$ar = self::$db->errorInfo();
return $ar[2] . ' (' . $ar[1] . '/' . $ar[0] . ')';
}
// returns one row fetched in FETCH_ASSOC mode
static public function fetchAssoc($sql) {
return self::$db->query($sql)->fetch(PDO::FETCH_ASSOC);
}
// returns one row fetched in FETCH_NUM mode
static public function fetchNum($sql) {
return self::$db->query($sql)->fetch(PDO::FETCH_NUM);
}
}
Для запуска БД надо написать:
Обычно я делаю это в подключаемом файле с классом, но тут зависит от стиля программирования, иногда стоит собирать инициализирующие мотор сайта функции в одном месте. Так как в большинстве случаев имя/пароль/название БД статические, они захардкожены в самом классе.
Единственная не универсальность, это:
catch (PDOException $e)
{
die('<h3 style="color: blue">Ошибка соединения с БД. Повторите попытку через полминуты</h3>');
}
Тут естественно Вам надо придумать свой обработчик ошибки открытия БД и уведомления пользователей, но в большинстве случаев сайт не может функционировать, и просит пользователя выполнить дополнительные действия.
Описание методов класса DB
Итак, вот описание всех методов класса:
DB::query($sql) – выполнить один запрос к БД с возвращением результата PDOStatement. его следует использовать только в том случае, если запрос не содержит необработанные данные, например из внешних источников. При использовании данных в запросе от внешних источников всегда следует применять подготовленные запросы (prepared statements) – смотрите ниже – в целях предотвращения SQL Injection (инъекции SQL запроса) и уверенности в целостности конечного запроса.
DB::exec($sql) – выполнение запроса, не требующего возврата результата. К нему относится тоже примечание, что и для DB::query. Так же его всегда следует использовать вместо DB::query, если запрос НЕ возвращает результата (обычно это UPDATE или DELETE), так как он, как указано, работает быстрее.
Возвращает кол-во затронутых строк.
DB::column($sql) – первая вкусность, которая упрощает жизнь. Выбор только одного значения из БД, соотв. запросу.
DB::columnInt($sql) – тоже самое, только возвращается intval значение поля выборки, что бывает полезно.
DB::prepare($sql) – подготовка запроса, возвращает PDOStatement.
DB::lastInsertId() – получение lastInsertId от последнего запроса INSERT.
DB::execute($sql, $ar) – подготавливает и сразу выполняет запрос. Каждый ? или :var в теле запроса должен соотв. значению из массива $ar. Пример смотрите выше. Возвращает true/false.
DB::error – возвращает строку последней ошибки над DB вместе с кодами в скобках. Помните, что при использования PDOSatement’ов ошибки, связанные именно с их исполнением, следует обрабатывать над ними, а не над общей DB.
И, наконец, две последние вкусности:
DB::fetchAssoc($sql)
DB::fetchNum($sql)
Возвращают результат выполнения $sql, в первом случае как PDO::FETCH_ASSOC, во втором – как PDO::FETCH_NUM.
Заметьте, что запрос НЕ подготовлен, поэтому можно использовать только с проверенными данными (НЕ из внешних источников). Но так как в большинстве случаев мы пишем запросы с корректными данными, эти функции часто бывают очень полезны и сокращают код программы.
Пример их использования
Получения данных клиента по его ID. Так как переменная ид у нас всегда целое число, использование его в запросе безопасно.
$id = intval($id); // обычно это уже сделано, то есть $id - у нас целая переменная,
// и не может содержать строки или ещё какой-нибудь мусор
$ar = DB::fetchAssoc("SELECT name,surname,email FROM cleints WHERE id=$id");
extract($ar);
// теперь в переменных $name, $surname, $email у нас необходимые данные клиента.
Ещё пример:
$id = intval($id); // обычно это уже сделано, то есть $id - у нас целая переменная,
// и не может содержать строки или ещё какой-нибудь мусор
list($name, $dtm) = DB::fetchNum("SELECT CONCAT(name, ' ', surname), DATE(dk_dtend)
FROM clients
WHERE DATE(dk_dtend)>'2007-01-01' AND id=$id");
Этот прием можно было использовать и в предыдущем примере, всё зависит от стиля, настроения и желания. Мы выбрали из таблицы два поля – имя ($name) и дату ($dtm) одним запросом. Здесь мы также уверенны, что подставляемая в запрос переменная $id не может содержать мусор или непроверенные данные.
Заключение
Ну, пока всё, в будущем сделаю ещё пост про HTML::render – также крутая штука, упрощающая жизнь PHP-программиста (который не использует сомнительные тяжёлые фрэймворки, а делает всё сам, своими руками, как это делаю я, в большинстве случаев). Хотя фрэймворки если уметь с ними работать могут упростить написание конечного кода, но они зачастую бывают слишком тяжелы и неповортливы, кроме того, небезопасны в использовании, так как в них могут находится уязвимости.
Но ничего не обещаю. Ещё Linux – компиляция программ с GCC/G++, make, cmake – ох, это чудо. GNU Utils, я люблю Вас! Сам не знаю за что.
До новых встреч – вкусного горячего кофе, удобной позы в работе, а самое главное – я не устаю это повторять, хотя в зубах завязло – отличного настроения! Это главное. Сейчас – отличное настроение. Не потом, не завтра, не в обед пятницы, не через год, на Гавайях, а сейчас. Отличное настроение – это бодрость духа – решает всё. ИМХО, конечно, по моему скромному мнению.
Удачи на даче!