ФорумПрограммированиеПыхнуть хотите?Готовые решения → Библиотека для работы с БД

Библиотека для работы с БД

  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 13 сентября 2007 г. 14:11

    Простенькая библиотечка для взаимодействия с базой данных.

    Качать тут (5 Кб).

    Update: 14.09.07 — заменена на версию 1.0.1 (изменения ниже по тексту).

    ТТХ:

    • Поддерживает использование только одной базы данных в сценарии.

    • Реализована для MySQL.

    • Предоставляет автоматическое экранирование данных.

    • Позволяет получать результат в удобном формате.

    • Для обработки ошибок задействованы исключения.

    • Требуется PHP5 с библиотекой mysql.


    [size=14]Формат библиотеки[/size]

    Библиотека состоит из одного файла (db.php).
    Основной функционал реализован в статическом классе DB.

    Работа с библиотекой осуществляется через вызовы статических методов класса:


    $return = DB::query('…');
    DB::fromId(10);

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

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

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 13 сентября 2007 г. 14:12, спустя 14 секунд

    [size=14]Выполнение запроса[/size]

    Основная функция библиотеки: выполнение sql-запроса к базе. Реализована она в виде метода query. Его формат:
    • mixed DB::query(string $pattern [, array $data [, int $fetch])

    В простейшем случае можно использовать только один аргумент, тогда функция аналогична mysql_query():

    DB::query('insert into `table` (`one`, `two`, `three`) values(1, 2, 3)');


    Одно из основных неудобств mysql_query() в том, что все входящие данные приходится экранировать бесконечными mysql_real_escape_string(), иначе можно получить sql-инъекцию или просто лишние проблемы.
    При вызове DB::query запрос можно разделить на шаблон ($pattern) и массив входных данных ($data).

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

    Варианты плейсхолдеров:

    • "?" — строковое данное. Перед вставкой, все опасные символы в нем экранируются, а само заключается в кавычки.

    • "?i" — целое число. Перед вставкой, значение принудительно приводится к числовому типу.

    • "?n" — данное с возможным NULL-значением. Если приходит null, то NULL и вставляется в запрос. В других случаях ведет себя так же, как и "?".

    • "?in" и "?ni" — целое число с возможным NULL-значением.

    • "?t" — имя таблицы.

    • "?c" — имя столбца.

    Вобще-то имена таблиц и столбцов не являются входными данными. Но, во-первых, теоретически более правильно отделить их от "шаблона запроса", во-вторых, это позволяет запросам остаться совместимыми при адаптации библиотеки под другие типы баз, в-третих, мне так больше нравится :).

    Для вставки символа вопроса, можно использовать последовательность "??".

    Плейсхолдеры в шаблоне, последовательно заменяются на входные данные.

    Пример:

    DB::query(
    'insert into ?t (?c, ?c, ?c) values(?i, ?, ?i)',
    Array('table', 'one', 'two', 'three', 1, 'Елки-"палки"', '3x')
    );
    // Выполняется запрос: insert into `table` (`one`, `two`, `three`) values(1, "Елки-\"палки\"", 3)


    Если в запросе используется несколько таблиц, то для столбцов можно указывать массив - (имя таблицы, имя столбца). При входном данном Array('tbl', 'col'), плейсхолдер "?c", будет заменен на "`tbl`.`col`".

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

    DB::setPrefix('prefix_');
    DB::query('select * from ?t', Array('table')); // select * from `prefix_table`
    DB::select('table', '`one`'); // select `one` from `prefix_table` where 1
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 13 сентября 2007 г. 14:12, спустя 8 секунд

    [size=14]Формат результата[/size]

    Второе основное неудобство работы через mysql_* функции — приведение результата в нужный формат. Обычно для этого используются громоздкие циклы с mysql_fetch_*().
    В DB::query() можно использовать третий параметр ($fetch) для указания в каком формате желательно получить результат. Возможные значения этого параметра, это:

    DB::fNot (по умолчанию) — никакой обработки. Возвращается старый добрый resource.

    DB::fBool — просто возвращается true в случае успеха. Может использоваться в запросах, которые ничего не возвращают.

    DB::fId — значение автоинкремента. Например:

    $id = DB::query('insert into ?t (?c,?c) values(?i,?i)', Array('tbl', 'one', 'two', 1, 2), DB::fId);
    // $id - идентификатор вставленной строки


    DB::fAR — количество затронутых запросом строк. Можно использовать, например, с UPDATE.

    DB::fNum — количество возвращенных запросом записей.

    DB::fRow — обработка результата по типу fetch_row(). На выходе порядковый массив, каждый элемент которого, соответствующих одной записи, порядковый массив полей.

    DB::fAssoc — аналогично, только записи соответствует ассоциативный массив "поле" => "значение".

    DB::fCol — используется при выборке по одному столбцу. Записи соответствует не массив, а значение поля.


    $names = DB::query('select ?c from ?t', Array('name', 'table'), DB::fCol); // Выборка имен из таблицы
    foreach ($names as $name) {
    print $name.'<br />';
    }


    DB::fRowRow — выборка одной строки (обычно по id). Возвращается порядковый массив полей данной строки, либо false, если такая строка не найдена.


    $row = DB::query('select * from ?t where ?c=?i', Array('tbl', 'id', 10), DB::fRowRow);
    if ($row === false) {
    print 'Строки с id=10 не существует';
    } else {
    print 'Строка найдена, вот её поля:';
    var_dump($row);
    }


    DB::fRowAssoc — аналогично, только возвращается ассоциативный массив.

    DB::fEl — значение единственного элемента при выборке одной строки, по одному столбцу. Так же false, если строка не найдена.


    print 'Логин юзера с номером 3: '.DB::query('select ?c from ?t where ?c=?i', Array('login', 'users', 'id', 3), DB::fEl);


    fIRow, fIAssoc, fICol — аналогично fRow, fAssoc и fCol, только возвращаются не массивы, а объекты-итераторы, которые так же можно пропускать, через foreach. Можно использовать, когда выборка слишком большая и может сожрать всю память:


    $res = DB::query('select * from ?t', Array('table'), DB::fIAssoc);
    foreach ($res as $el) {
    print 'Параметры очередной строки:';
    var_dump($el);
    }
    $res->count(); // Возвращает общее количество записей
    $res->get(100); // Позволяет получить запись по номеру (от 0)
    $res->get(100, 'column'); // Позволяет получить одно поле ("column"), записи за номером 100.
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 13 сентября 2007 г. 14:12, спустя 9 секунд

    [size=14]Специфические методы[/size]

    Простые, распространенные запросы

    [size=12]Выборка данных (select)[/size]

    mixed DB::select(string $table, mixed $cols, mixed $where, mixed $orderBy, mixed $limit, mixed $other, int $fetch);

    Указывается имя таблицы и основные разделы запроса SELECT. Разделы можно пропускать. По умолчанию результат возвращается в виде ассоциативного итератора (DB::fIAssoc), можно изменить установкой аргумента $fetch.


    DB::select('tbl'); // select * from `tbl` where 1 - выбрать всё.


    $cols указывет список столбцов для выборки. Может быть простой строкой или порядковым массивом.


    DB::select('tbl', '`one`,`two`'); // select `one`,`two` from `tbl` where 1
    DB::select('tbl', Array('one', 'two')); // –//-//-


    $where, $orderBy, $limit — соответствующие разделы запроса. Если не указываются или false — пропускаются. Могут быть простой строкой или массивом из двух элементов (шаблон, входные данные), обрабатываемым, аналогично плейсхолдерам в DB::query().


    DB::select('tbl', '*', '`id`=10'); // select * from `tbl` where `id`=10
    DB::select('tbl', '*', Array('?c=?i', Array('id', 20))) // select * from `tbl` where `id`=20
    DB::select('tbl', '*', 1, '`ordr` asc', Array('?i, ?i', Array(0, 10))); // select * from 'tbl' where 1 order by `ordr` asc limit 0, 10


    $other позволяет добавить другие разделы к запросу:

    DB::select('tbl', '*', 1, false, false, 'group by `one`') // select * from `tbl` where 1 group by `one`



    [size=12]Вставка данных (insert)[/size]

    int DB::insert(string $table, array $data);
    int DB::insert(string $table, mixed $cols, mixed $vals);

    Метод можно вызывать в двух форматах. Первым аргументом, как видно, в обоих случаях передается имя таблицы, в которую вставляем. В обоих случаях возвращается значение автоинкремента вставленной строки (если он есть для таблицы).

    1. Вставка данных из ассоциативного массива:

    $data = Array(
    'one' => 11,
    'two' => 22
    );
    $id = DB::insert('tbl', $data); // insert into `tbl` (`one`, `two`) values ("11", "22")
    print 'Идентификатор новой строки: '.$id;


    2. Отдельное укзание cols и vals


    /* Просто строками */
    DB::insert('tbl', '`one`, `two`', '1, 2'); // insert into `tbl` (`one`, `two`) values (1, 2)

    /* Порядковыми массивами */
    DB::insert('tbl', Array('one', 'two'), Array(1, 2)); // insert into `tbl` (`one`, `two`) values ("1", "2")


    3. Для вставки нескольких строк одним запросом можно в vals передать массив массивов:


    $data = Array(
    Array(1, 2),
    Array(3, 4),
    Array(5, 6),
    );

    // insert into `tbl` (`one`, `two`) values ("1", "2"), ("3", "4"), ("5", "6")
    $id = DB::insert('tbl', Array('one', 'two'), $data);
    print 'Последняя вставленная строка: '.$id;



    [size=12]Обновление данных (update)[/size]

    int DB::update(string $table, mixed $set, mixed $where)

    Формат $where аналогичен DB::select().
    $set может быть следующим:

    /* update `tbl` set `one`=1, `two`=3 where `id`=2 */
    DB::update('tbl', '`one`=1, `two`=3', '`id`=2'); // Просто строка
    DB::update('tbl', Array('?c=?i, ?c=?i', Array('one', 1, 'two', 3)), '`id`=2'); // Массив (шаблон, данные)

    $set = Array(
    'one' => 1,
    'two' => 3,
    );
    DB::update('tbl', $set, '`id`=2'); // Ассоциативный массив - столбец => значение


    Функция возвращает количество затронутых строк.


    [size=12]Удаление данных (delete)[/size]

    int DB::delete(string $table, mixed $where)

    Удаляет из таблицы $table, строки соответствующие условию $where (формат аналогичен DB::select). Возвращает количество удаленный строк.


    DB::delete('tbl', '`id`>10'); // delete from `tbl` where `id`>10


    Update 1.0.1: При пропуске $where, таблица очищается через TRUNCATE. (По наводке Sergey89)


    [size=12]Подсчет количества записей[/size]

    int DB::count(string $table, string $where [, array $data]);


    print DB::count('tbl'); // select count(*) from `tbl` where 1
    print DB::count('tbl', '`id`>3'); // select count(*) from `tbl` where `id`>3
    print DB::count('tbl', '?c<?i', Array('id', 7)); // select count(*) from `tbl` where `id`<7


    [size=14]Работа с записями по идентификатору[/size]

    Для строк в таблицах, имеющих первичный ключ, можно использовать функции данного раздела.
    По умолчанию именем столбца-ключа является "id" (константа DB::rowId - можно поменять).
    Удобное, если у большинства таблиц первичный ключ имеет одно название.

    mixed DB::fromId(string $table, int $id [, string $row, string $rowId])


    DB::fromId('tbl', 10); // select * from `tbl` where `id`=10 - получаем ассоциативный массив, соответствующий строке
    DB::fromId('tbl', 10, 'one'); // select `one` from `tbl` where `id`=10 - получаем значение столбца 'one' заданной строки
    DB::fromId('tbl', 10, false, 'id2'); // select * from `tbl` where `id_other`=10 - меняем имя столбца-ключа



    mixed DB::deleteId(string $table, int $id [, string $rowId]) - удаление строки по id
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 13 сентября 2007 г. 14:12, спустя 10 секунд

    [size=14]Конфигурация базы данных[/size]

    Конфигурация базы задается ассоциативным массивом с индексами "host", "user", "password", "dbname".

    Конфигурацию можно указать двумя способами:

    1. Жестко в коде библиотеки. Найти определение скрытого свойства $config, класса DB и переопределить. Если все сценарии, использующие данную библиотеку, работают с одной базой, этот способ самый оптимальный (не считая идеологических причин выносить конфигурацию отдельно).

    2. Устанавливать конфиг вручную в сценарии.
    Для этого нужно, во-первых, разрешить данную процедуру, установив свойство $configChangeable в true, а во-вторых, вызвать нужный метод:


    $config = Array(
    'host' => 'xxx',
    'user' => 'xxx',
    'password' => 'xxx',
    'dbname' => 'xxx',
    );
    DB::setConfig($config);


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


    Подключение

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


    [size=14]Отладка[/size]

    Вызов DB::setDebug(true) включает отладку. DB::setDebug(false) отключает.
    Во время отладки, содержимое запросов будет выводиться в браузер.
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 13 сентября 2007 г. 14:12, спустя 6 секунд

    [size=14]Обаботка ошибок[/size]

    В библиотеки для ошибочных ситуаций используется генерация исключений.

    Все исключения библиотеки наследуются от DBException.


    [size=12]DBExceptionQuery[/size]

    Наиболее важное исключение. Генерируется при ошибке выполнения запроса. Из объекта исключения можно вытащить запрос, код ошибки и описание:


    try {
    DB::query('abrakadabra');
    } catch (DBExceptionQuery $e) {
    print $e->query().' '.$e->errno().' '.$e->error();
    }


    Кроме DB::query() выбрасывается так же всеми остальными методами, такими как DB::select, DB::insert…

    Все остальные исключения библиотеки не определяют никаких новых методов, а только переопределяют сообщение об ошибке.


    [size=12]DBExceptionConnect[/size]

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

    От него наследуются DBExceptionConnectHost (не подрубить сервер) и DBExceptionConnectBase (не подключиться к базе), для более тонкой обработки :)

    [size=12]DBExceptionConfig[/size]

    Исключение при изменении конфига. Производные от него:

    DBExceptionConfigChange - попытка изменение при выключенном свойстве configChangeable.
    DBExceptionLoaded - попытка повторного изменения, когда конфигурация уже загружена.


    [size=12]DBExceptionData[/size]

    Несоответствие количества входных данных и плейсхолдеров в шаблоне.

    DBExceptionDataMuch - данных больше
    DBExceptionDataNotEnough - данных меньше
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 13 сентября 2007 г. 14:13, спустя 15 секунд

    Фигасе нафигачил… :)
  • Patrick

    Сообщения: 506 Репутация: N Группа: Кто попало

    Spritz 13 сентября 2007 г. 16:14, спустя 2 часа 1 минуту 47 секунд

    1. Зачем на каждое исключение класс? не проще

    <?php
    class DBException extends Exception
    {
    public function __construct($msg)
    {
    $this->message = $msg;
    }
    }
    ?>

    2. Если есть Итераторы зычем массивы?
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 14 сентября 2007 г. 2:34, спустя 10 часов 19 минут 49 секунд

    1. Запрос к базе может сгенерировать несколько исключений — сам запрос ошибочный, несоответствие количества данных и вопросиков, невозможно подключиться к базе. Иногда отлавливать нужно только отдельные из них, а остальные пропускать. Либо, обрабатывать по разному.

    2. Массивы удобнее.
  • Patrick

    Сообщения: 506 Репутация: N Группа: Кто попало

    Spritz 14 сентября 2007 г. 6:56, спустя 4 часа 22 минуты 13 секунд

    Запрос к базе может сгенерировать несколько исключений — сам запрос ошибочный, несоответствие количества данных и вопросиков, невозможно подключиться к базе. Иногда отлавливать нужно только отдельные из них, а остальные пропускать. Либо, обрабатывать по разному.

    во всех классах исключений кроме DBExceptionQuery, меняется только message, не прощили передовать их(сообщения) в конструктор, как в базовом классе Exception сделанно…
  • vasa_c

    Сообщения: 3127 Репутация: N Группа: в ухо

    Spritz 14 сентября 2007 г. 7:06, спустя 9 минут 58 секунд

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

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

    try {
    DB::query('…');
    } catch (DBExceptionConnect $e) {
    // Не сработал коннект, выводим свою красивую страничку об ошибке.
    exit();
    }
    // Ошибки запроса должны быть устранены на этапе разработки, поэтому они проваливаются ниже.
  • Dagdamor

    Сообщения: 47 Репутация: N Группа: Кто попало

    Spritz 16 сентября 2007 г. 13:51, спустя 2 дня 6 часов 44 минуты

    Согласен с vasa_c, исключения, соответствующие разным ситуациям, должны иметь разные типы.
    Насчет итератора тоже согласен, пусть будет хоть одна библиотечка, действительно удобная в использовании, без ненужных ООП-наворотов.
    В целом неплохо. :)
  • Patrick

    Сообщения: 506 Репутация: N Группа: Кто попало

    Spritz 16 сентября 2007 г. 15:40, спустя 1 час 49 минут 56 секунд


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

    [offtop]
    Вот скажи мне, какую ты знаешь нормальную библиотеку для БД с ненужными ООП наворотами?????
    а плодить несколько классов с разными именами, это не навороты?????
    [/offtop]
  • Dagdamor

    Сообщения: 47 Репутация: N Группа: Кто попало

    Spritz 16 сентября 2007 г. 18:52, спустя 3 часа 11 минут 4 секунды

    Если это исключения - тогда нет.
    Любой ОО-движок с библиотекой БД, в которой есть понятие "критерий" (и как крайне тяжелый случай, "операция" и "операнд").
  • Patrick

    Сообщения: 506 Репутация: N Группа: Кто попало

    Spritz 17 сентября 2007 г. 7:03, спустя 12 часов 11 минут 56 секунд


    Если это исключения - тогда нет.
    Любой ОО-движок с библиотекой БД, в которой есть понятие "критерий" (и как крайне тяжелый случай, "операция" и "операнд").

    Ну-ну….

Пожалуйста, авторизуйтесь, чтобы написать комментарий!