Абстракция БД

    Когда я только начинал писать на PHP, ООП я владел, пожалуй, на уровне синтаксиса, не более. В то время мне вполне хватало функций mysql_*. Со временем, опыта становилось больше, я начинал задумываться об оптимизации. Этот топик описывает эволюцию моего кода.

    Первым шагом стало написание простенького класса, который облегчал работу с БД. Кода стало не на много меньше, в основном упрощались типовые запросы. Также, автоматически экранировались входные параметры. Бонусом, появилась возможность использовать код с БД, отличных от MySQL (написав класс с таким же интерфейсом). По крайней мере, большинство запросов переписывать не пришлось бы. Код выглядел примерно так:
    $db = new MysqlDb($server, $user, $pass, $db, $prefix);
    $db->select($key, $value); // Возможно было много вариантов запроса, используя различное количество параметров.
    while ($db->fetch()) {
       $row = $db->getRow();
       …
    }
    Либо $rows = $db->getResult();
    * This source code was highlighted with Source Code Highlighter.

    Чтобы еще упростить работу с БД, я стал использовать Active Record. Мое решение во многом проигрывало сторонним, но мне тогда этого хватало. В то время появился Zend Framework, поэтому некоторые идеи взяты с него. Сами классы работали через PDO (хотя, библиотека для работы через php_mysql тоже имелась).
    Пример кода:
    $db = Cms_Db::factory('MySQL');
    $db->connect($server, $user, $pass, $db, $prefix);
    $element = $db->select('id', $id);
    $element->name = 'New name';
    $element->update();
    * This source code was highlighted with Source Code Highlighter.

    Этот вариант прожил довольно долго. Но со временем и его стало не хватать. Класс развивался экстенсивно, добавлялись новые методы для обработки типовых запросов. К тому времени, вышел стабильный релиз Zend Framework. Стало надоедать постоянно добавлять новые типы запросов.

    Со временем я начал писать модели под основные объекты сайта. Внутри все еще работал мой старый класс, но снаружи все операции выглядели довольно прозрачно. Такой подход мне понравился, и чтобы упростить реализацию, я решил использовать ORM. Вначале, присматривался к Doctrine и Propel, но в конце решил написать свою реализацию.

    Работа с БД осуществляется через Zend_Db, для гибкости также применяется Zend_Db_Select. Описание модели во многом похоже на Doctrine. Но у меня модель содержит также данные, предназначенные для описания представления некоторых данных. Приведу пример:
    class Model_News extends Cms_Essence_Model
    {
       public function setDefinition()
       {
          $this->addField('name', 'Заголовок')
             ->addField('link', 'Адрес', 'link')
             ->addField('date', 'Дата публикации', 'date')
             ->addField('description', 'Краткое описание', 'text,255', array(
                'validate' => 'isVoid',
                'detail' => true
             ))
             ->addField('content', 'Содержание', 'editor', array(
                'detail' => true,
                'minLength' => 40,
                'validate' => array('isVoid', 'minLength'))
             ))
             ->setOption('editorTableClass', 'newsTable');
       }
    }
    $element = Cms_Essence::get('Model_News')->getById(2);
    * This source code was highlighted with Source Code Highlighter.

    Как и в полноценных ORM, также можно описать связи таблиц и использовать код вроде:
    $user = Cms_Essence::get('Model_User')->getJoined('Model_Group', 'name', 'megahertz');
    $groupName = $user->group->name;
    * This source code was highlighted with Source Code Highlighter.

    Конечно, по удобству таких запросов мне еще далеко до полноценных ORM, но пока меня такой вариант устраивает.

    Такой подход позволяет писать минимум кода. Имея описанную модель, я могу использовать ее в админке, при этом не написав ни строчки кода. Причем админка может быть любого тип, классическая, ajax редактор на сайте, через RPC на другом сайте и т.п. Также можно использовать компоненты в шаблонах, чтоб не писать код. Например, на Smarty компонент будет выглядеть так:
    {state model=Model_News name=news limit=3}
    <ul>
    {list from=$news}
       <p>
          <b>{$newsList->name} {$newsList->date}</b><br />
          {$newsList->description}<br />
          <a href="{$newsList->getUrl()}">подробнее</a>
       </p>
    {/list}
    * This source code was highlighted with Source Code Highlighter.

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

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

    Поделиться публикацией

    Комментарии 48

      +1
      Для элементарных запросов абстракция подходит, а вот для сложных... Можно глянуть код mysql класса IP.Board. На мой взгял, это ООП ради ООП (я иею ввиду имеено mysql модуль). Хотя, возможно, для безопасности это плюс.
        0
        Хм, по-моему в первом коде это "классы и методы ради ООП", хотя это, конечно, не ООП. А вот последний код, имхо, показывает удобство ООП в грамотных руках.
          0
          Насчет последнего: смысл делать ООП, если можно стандартной функцией превратить мускульную строку в массив? Т.е. получится что-то вроде:
          {list from=$news}

          {$newsList['name']} {$newsList['date']}
          {$newsList['description']}
          подробнее

          {/list}
            0
            В PHP массивы похожи на объекты, по сути - и то и то - хэш-таблицы(очень условно отбрасывая многие навески объектов). Но вот как их использовать - в первом случае вроде используются объекты, но если посмотреть - код чисто процедурный. Дело не в инструментах, дело в идее:)
            Таки отвечая на ваш ответ "смысл?". Да, можно было взять массив, урл брать из какой-нибудь функции... Вроде проблем нет, кроме вмешательства в глобальную область видимости. Кстати, для смарти пришлось бы писать хелпер, в общем, плюсов у этого подхода в данном случае всё-таки больше, имхо.
              0
              Это не совсем массив. В модели можно переопределить возвращаемое значение любого поля, написав метод get<Имя поля>. К тому же, компонент автоматом преобразует значение из БД в строковое (к примеру, timestamp в человекопонятный вид), опираясь на тип поля.
            0
            Я оставил возможность использовать модель в обычных запросах через Zend_Db_Select. К примеру
            $model = Cms_Essence::get('Model_News');
            $model->select()->limit(10, 20);
            $elements = $model->execute();
              0
              Объясните, пожалуйста, этот кусок "$model->select()->limit(10, 20);" - каким образом функция limit() является частью функции select()? Ни разу с таким не встречался, нагуглить не смог, так как не имею представления как такое называется.
                +1
                делается такой эффект очень просто:
                class Foo {
                  function bar() {
                    return $this;
                  }
                }
                $foo = new Foo();
                $foo->bar()->bar()->bar();
                  0
                  спасибо, даже близко такое в голову не приходило (:
                  p.s. - не зря начало работы совместил с регистрацией на хабре (:
            0
            Эволюция описана наглядно:) От процедурного подхода до действительно ООП:)
            Если не секрет, сколько между первым и последним кодами в годах/месяцах?
              +1
              Приблизительно с начала 2006
            • НЛО прилетело и опубликовало эту надпись здесь
                0
                Необязательно. Абстракция - ключ к масштабируемости:)
                  +3
                  А после этого чудного флешбека придет понимание, что смысла дергать каждый раз базу данных - вообщем-то и нету, многое можно просто закэшировать.

                  А менять удобство и скорость разработки на написание десятка тыщ строк кода, который сложен в поддержке... думаю прикупить еще один сервер - будет самым разумным решением.
                    0
                    а чем плох mysqli? у нас в коде враппер для БД построен именно как потомок mysqli класса, с переопределением функций для добавления нужного функционала. Тормозов на частых запросах к БД нет, хотя система используется весьма напряжённо.
                      0
                      Сейчас уже без разницы, через какую либу работать с БД. Раньше не юзал mysqli, так как мой любимый хостер его не поддерживал, зато поддерживал PDO.
                    +1
                    Если не ошибаюсь в
                    "
                    $bd = Cms_Db::factory('MySQL');
                    $db->connect($server, $user, $pass, $db, $prefix);
                    $element = $db->select('id', $id);
                    $element->name = 'New name';
                    $element->update();
                    "
                    Перепутал $bd с $db
                    Мелочь, но все же)
                      +1
                      Плюс к этому в методе Cms_Essence_Model::getDefinition лишняя ; и не хватает )
                      0
                      А мне лично кажется паттерн DataMapper более удобным нежели различные реализации ORM, все-таки у каждого СУБД есть свои специфичные особенности, которые в общем ORM'е все учесть сложно, и DataMapper в этом плане предоставляет большие возможности для использования сложных, составленных вручную запросов с учетом специфики СУБД. При переходе на другой СУБД DataMapper конечно придется переписывать, но я лично считаю это оправданным, в мелких проектах это не так много работы, а в крупных не так часто меняют СУБД, и там как раз таки очень важна производительность. Ну и при необходимости использования нескольких вариантов хранения информации - DataMapper можно инстанциировать через фабрику.
                        +1
                        Тут есть готовый лисапед phpdoctrine.org, кто нить пользовался?
                          +2
                          Да. Кривой, глючный и неудобный. Но лучше всех других таких лисапедов, правда.
                            0
                            Из лисапедов в PHP мне нравится Propel - классная вещь
                              0
                              Я на дух не переношу оарэмы, в которых сначала надо описать структуру во внешнем файле, а потом на этой базе создаются PHP-классы. Я неправ?
                                0
                                Ну например использую hibernate можно писать все в одном файле с классами - аннотациями завется, правдо к php это не имеет небольшое отношение, видно жавакодеры тоже не навидили разнесение всего по отдельным файлам :)
                                  +2
                                  Я использовал DBDesigner, его XML через XSLT в нужный пропелу формат конвертил. Все автоматизированно - за секунду изменения из DBDesigner накатывались на классы пропела, оказалось очень удобно. Структуру описываешь в ERM диаграмме.
                            0
                            ORM имеет большой минус, я не видел ни одного который бы нормально работал со сложными джоинами.
                            ЗЫ: Кстати если знаете, подсуньте ссылку.
                              0
                              DBIx::Class :)
                                0
                                Ага, а чтобы оно нормально работало надо для каждого класса написать по килобайту кода =)
                                  0
                                  мне слегка раздражает попытки основываться на названиях таблиц и использования inflation, но для него есть DBIx::Class::Schema::Loader. И писать ничего не надо :) только при условии что поля правильно названы
                              0
                              А потом придет осознание что весь сложный код для работы с БД, надо хранить в самой БД.
                              И из приложения просто его вызывать.
                                +1
                                Такие задачи не так часто встречаются и под них обычно нет смысла писать универсальные решения
                                  +2
                                  Не так часто, это от области работы сильно зависит.
                                  Я только с такими и работаю.
                                  Получается что для простейших действий, например, взять строку - показать, обновить то что ввел юзер. Это проще делать через объектную модель.

                                  А вот сформировать отчет, загрузить/выгрузить данные во внешний источник, рассчитать нечто. Это все делается через хранимые процедуры и никак иначе. Главное вовремя разобраться что и как делать лучше.
                                –1
                                Очередная статья ни о чём... может каждый напишет, что с 2006-го он стал лучше кодить ??
                                  +2
                                  Да пусть пишут, пример про змейку с зенд фреймворком мне лично понравился :)
                                  А тут я конечно ничего нового не узнал, но всёравно спасибо автору что не поленился написать!
                                  –1
                                  я так смотрю в итоге ORM получилась...а почему б пропел не взять сразу?
                                    0
                                    мне он не очень понравился
                                    0
                                    А Вы используете bind_variables и умеете-ли регулировать fetch_size ?
                                    Если нет, то Вы отходите от масштабирования совершенно в другую сторону. :(
                                      0
                                      bind variables через Zend_Db, fetch size нет, все равно в основном с MySQL работаю
                                      0
                                      Вдумчиво читаем весь тред и, особенно, статью про объектно-реляционное отображение.
                                        0
                                        чёрт его знает, но мне всегда хватало $db = new mysqli(...);
                                          0
                                          Не знаю, как вам, а мне больше нравится, когда я сам, ЛИЧНО, пишу тело запроса и отправляю его на выполнение.
                                          $this->db->execute('select user_id from users');
                                          Мне кажется так намного понятней выглядит код в моделях и можно сразу, практически мгновенно найти нужный запрос в коде.
                                            0
                                            Зато у Вас никакой абстракции от БД нет и если синтаксис запросов поменяется в связи с переездом mysql на что-то другое, то и будете в моделях править ВСЕ ЗАПРОСЫ.
                                              0
                                              А вам часто надо на проекте менять субд? :)
                                              Может быть у меня слишком мало опыта - но у меня не было такого случая ни разу в жизни. И думаю, что в общем случае будет максимум 5-10% таких проектов, на которых надо менять СУБД.
                                                0
                                                врядли надо будет менять в проекте, тут Вы правы.
                                                НО быстрее будет использовать некую абстракцию, которая в новых проектах на НОВОЙ СУБД позволит Вам без труда включиться в процесс разработки. В общем я хочу сказать, что во всякого рода фреймворках абстракция необходима
                                                  0
                                                  а. с этим я полностью согласен )
                                                  0
                                                  Присоединяюсь, лично мне всегда хватало DbSimple, по моему очень удобный и быстрый класс для работы с БД. И абстракцию, имхо в реальной жизни практически не востребованную (кроме универсальных фреймворков), предоставляет, и позволяет и полностью контролировать запросы и данные в безопасном виде позволяет вставлять / получать, и кешировать умеет нативно, чего еще надо, никогда не понимал.
                                                  С нетерпением жду PHP5.3 stable, что бы пощупать обновленный MySQLi
                                                0
                                                А ещё лучше (имхо) выглядит код
                                                Dim userIDs = From u in db.Users Select u.user_id
                                                И о вечных SQL injection'ах, которые постоянно находятся в кое-каком софте, можно забыть.
                                                0
                                                Чего только люди не выделывают. Лишь бы делать вид, что LINQ2SQL не существует.

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