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

CoolKid

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

Мне нужен простой, быстрый и проверенный класс-обертка над PDO, в которой бы был простой query builder, возможно с функциональностью DBAL, но можно и без неё.

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

Поиск на Гитхабе и PHPClasses по запросу "pdo query builder" не блещет обилием и качеством результатов.

Doctrine DBAL не предлагать, слишком много ресурсов жрет данная библиотека.

Ну и вообще, хотелось бы услышать мнения по вопросам:
pure SQL vs ORM
логика в базе vs логика в приложении

и последний вопрос: если бы вы создавали свой "идеальный" и "принципиально новый фреймворк" как бы вы поступили? Писали бы запросы на SQL+PDO (потому что так быстрее работает) или использовали бы ORM (потому что так моднее и "правильнее") или оставили бы выбор пользователям вашего фреймворка, обеспечив функциональность и того и другого подхода?
 

Вурдалак

Продвинутый новичок
plain SQL и ORM друг друга не исключают. Вот если у тебя есть сущности Director и Movie, то на бизнес требование вывести имена всех режиссёров с их общим суммарным бюджетом всех их фильмов, ты ORM'ом воспользоваться сможешь вряд ли, потому что она не предназначена для проекций. Ты напишешь «SELECT d.name, SUM(m.budget) ...» и результат этой выборки — ни Director, ни Movie. Но вручную маппить результат выборки на сущность на запросах типа «SELECT ... FROM movies WHERE id = 42» — это тоже убого.
 

hell0w0rd

Продвинутый новичок
Лучший - который сам напишешь)
На ту же доктрину с одной стороны плюешься, а с другой - радуешься возможности писать select User u where u.id = 100500 и получать объект.

Вурдалак, http://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html#new-operator-syntax

Вообще самая фишка любой ORM - модифицированный SQL/query-builder, в тот же момент и ее минус, тк ограничивает разработчика в используемых SQL-конструкциях.
 

CoolKid

Новичок
Интересно, а какой средний процент использования plain SQL запросов в случае использования ORM?
 

fixxxer

К.О.
Партнер клуба
Средняя температура по больнице интересует?

Вот есть, скажем, блог с комментариями, а есть система сбора статистики и генерации отчетов.
 

CoolKid

Новичок
Кстати очень удачный пример.
Если с блогом все понятно - я полагаю что его полностью можно реализовать используя только ORM без plain SQL, то со статистикой и отчетами ситуация неоднозначная: я в свое время этим плотно занимался и ещё не забыл километровые SQL с 15-ю джоинами, хэвингами, груп_баями итд.

Но ведь можно сформировать представления, процедуры, пакеты на Pl/SQL, Pl/PgSQL итд, а в коде вызывать уже их через ORM.
Вроде бы логично и удобно, но имеются противники данного подхода, которые утверждают, что вся логика должна быть в коде.
 

Absinthe

жожо
Doctrine DBAL.
Кривоватая, но лучше ничего не видел.

Ну и вообще, хотелось бы услышать мнения по вопросам:
pure SQL vs ORM
В зависимости от того, нужны ли сущности в приложении. Если нужны - однозначно ORM, если нет (отчеты и т.д.) - то чистый SQL (возможно, через Query Builder, который без ORM)

логика в базе vs логика в приложении
Мое ИМХО: логики в базе быть не должно. Затрудняет поддержку.
 

CoolKid

Новичок
Мое ИМХО: логики в базе быть не должно. Затрудняет поддержку.
Допустим у нас есть вот такой вот запрос (выдернуто из реально работающего проекта)
Код:
SELECT
  `contests`.`id` AS `id`,
  `contests`.`title` AS `contest`,
  `contests`.`status` AS `status`,
  `nomin`.`name` AS `nomination`,
  `nomin`.`id` AS `nomination_id`,
  `ages`.`name` AS `age_cat`,
  `ages`.`id` AS `age_id`,
  `fp`.`name` AS `prize_place`,
  `fp`.`id` AS `prize_id`,
  `fpp`.`value` AS `prize_points`,
  (SELECT
      `fw`.`id`
    FROM `forpost_works` `fw`
    WHERE ((`fw`.`contest_id` = `contests`.`id`)
    AND (`fw`.`nomination_id` = `nomin`.`id`)
    AND (`fw`.`age_id` = `ages`.`id`)
    AND (`fw`.`prize_id` = `fp`.`id`))) AS `work_id`,
  (SELECT
      `fw`.`title`
    FROM `forpost_works` `fw`
    WHERE ((`fw`.`contest_id` = `contests`.`id`)
    AND (`fw`.`nomination_id` = `nomin`.`id`)
    AND (`fw`.`age_id` = `ages`.`id`)
    AND (`fw`.`prize_id` = `fp`.`id`))) AS `work_title`,
  (SELECT
      CONCAT(`usr`.`last_name`, ' ', `usr`.`first_name`, ' ', `usr`.`second_name`, ' ', `usr`.`learning_place`, ' ', `usr`.`city`)
    FROM (`forpost_works` `fw2`
      LEFT JOIN `forpost_users` `usr`
        ON ((`fw2`.`user_id` = `usr`.`userid`)))
    WHERE ((`fw2`.`contest_id` = `contests`.`id`)
    AND (`fw2`.`nomination_id` = `nomin`.`id`)
    AND (`fw2`.`age_id` = `ages`.`id`)
    AND (`fw2`.`prize_id` = `fp`.`id`))) AS `user_fullname`,
  (SELECT
      `usr`.`userid`
    FROM (`forpost_works` `fw2`
      JOIN `forpost_users` `usr`
        ON ((`fw2`.`user_id` = `usr`.`userid`)))
    WHERE ((`fw2`.`contest_id` = `contests`.`id`)
    AND (`fw2`.`nomination_id` = `nomin`.`id`)
    AND (`fw2`.`age_id` = `ages`.`id`)
    AND (`fw2`.`prize_id` = `fp`.`id`))) AS `user_id`
FROM (((((`forpost_contest_link` `con_link`
   JOIN `forpost_contests` `contests`
    ON ((`con_link`.`contest_id` = `contests`.`id`)))
  JOIN `forpost_nominations` `nomin`
    ON ((`con_link`.`nomination_id` = `nomin`.`id`)))
   JOIN `forpost_ages` `ages`
    ON ((`con_link`.`age_id` = `ages`.`id`)))
   JOIN `forpost_prize_prop` `fpp`
    ON ((`con_link`.`contest_id` = `fpp`.`contest_id`)))
   JOIN `forpost_prizes` `fp`
    ON ((`fpp`.`prize_id` = `fp`.`id`)))
ORDER BY `nomin`.`id`, `ages`.`id`, `fp`.`id`
возможно он написан не оптимально, а возможно и оптимально, суть не в этом.
Суть в том, что он работает, выполняет свою задачу, при этом достаточно быстро.

Что делать с таким запросом?
Выразить его как он есть через ORM? Я даже не буду пытаться это сделать.
Собрать в Query Builder? Я не знаток Doctrine DBAL и не знаю, сможет она такое "переварить" или нет, предполагаю, что нет.
Разбить на подзапросы, запихать их в ORM и оперировать сущностями в коде? Можно, при этом кол-во кода нереально увеличиться по сравнению с текущей ситуацией.
Выполнить plain SQL запрос? Можно, однако такая портянка в коде некошерна с точки зрения Дзена.
И наконец, запихнуть это в представление (как оно сейчас и есть) и выполнить SELECT * FROM getContestResults хоть в билдере, хоть в mysql_query()

Почему при последнем подходе затруднится поддержка? Потому что девелопер вынужден будет лезть в базу и "пропарсить в уме" sql-код представления? Это так сложно? Или современные девелоперы - это узкозаточенные специалисты с ООП головного мозга, для которых база данных это темный ящик и они не должны знать что там и как?
 

Absinthe

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

И для Query Builder я использую только простые запросы. Такие - только SQL. В виде prepared statements. В query builder все будет лишь сложнее.
Тут ИМХО оптимальное решение: голым запросом.

А последний абзац - слишком много жира :D
 

hell0w0rd

Продвинутый новичок
CoolKid, чтобы такой запрос обработать в ORM, если говорить о доктрине, можно написать ResultSetMapping. Вы получаете объект, в который передаются результаты запроса, а на выходе он отдает объекты.
Запихать ваш запрос куда угодно (вьюха, процедура) можно в миграциях.
Под логикой в базе я имел ввиду триггеры, хранимки и прочую муть.
А на мой взгляд наоборот. Есть реальная задача - комментарии и комментируемая сущность. У комментируемой сущности есть рейтинг, который выстраивается по хитрой формуле, я могу его вычислять каждый раз в выборке, а могу кешировать в поле rating. Чтобы в доктрине это сделать - мне приходится писать sql запрос в php, спрашивается - нахрена?
Хотя я был в одной компании, которая работала с Oracle и отсылала http-запросы сервису отправки смс прямо из базы, тут уже реально перебор))
 

CoolKid

Новичок
Хотя я был в одной компании, которая работала с Oracle и отсылала http-запросы сервису отправки смс прямо из базы, тут уже реально перебор))
Когда я работал в N-ском филиале компании МТС, у нас была замечательная биллинговая система, которая была ПОЛНОСТЬЮ написана на Oracle Pl/Sql
Т.е. биллинг представлял из себя over 9000 таблиц и over 900 пакетов, в которых реализована вся логика, включая отправку запросов на Cisco с помощью сокетов и генерацию html контента для личного кабинета. Интерфейс оператора при этом был сделан на Oracle Forms.

Когда я впервые увидел это - у меня был шок, но поработав с этим некоторое время понял, что такая архитектура имеет право на существование при этом весьма успешное)
 

fixxxer

К.О.
Партнер клуба
CoolKid, если из базы каким-то образом достались данные, и ими заполнились свойства объектов, - это уже ORM.

Это 10 лет назад в среде так называемых веб-разработчиков никто не понимал, что такое "модель", и класс с имением Foo_Model имел методы, которые делали запросы к базе и отдавали наружу массивы. Сейчас такое уже сложно встретить, и в любом случае на Model-уровне так или иначе ты оперируешь с заполнением, созданием или чтением из объектов или их коллекций.

Соответственно, в том или ином виде Object-Relational Mapping у нас есть всегда. Остается лишь вопрос его реализации.

Что касается логики в базе - такое несомненно имеет право на существование, если, как минимум, весь уровень Model - а лучше вообще весь application server - перенесен в базу. Подобные возможности в сколь-либо юзабельном виде есть только в коммерческих СУБД, а полноценны вообще только в Oracle (может быть, еще в db2 - давно его не видел), при этом писать придется на недоязыках типа pl/sql. Для биллинговой системы, состоящей на 99% из сложных запросов к базе, это может иметь смысл, для веба - вряд ли.
 
Последнее редактирование:

CoolKid

Новичок
Это 10 лет назад в среде так называемых веб-разработчиков никто не понимал, что такое "модель", и класс с имением Foo_Model имел методы, которые делали запросы к базе и отдавали наружу массивы. Сейчас такое уже сложно встретить
fixxxer, правильно ли я тебя понял: если у нас есть сейчас какой-то фреймворк, в котором мы увидим следующее:

PHP:
<?php
class PostsController extends WebController
{
    public function actionIndex()
    {
      $this->PostsModel->getPosts(); // calls method PostsModel::getPosts which fetches data from database and puts it into Array(!)
      $data_generated_in_model = $this->PostsModel->getData(); // gets data from model; variable $data_generated_in_model is Array(!)
      $this->PostsView->setData($data_generated_in_model); // puts data from model in view
      $content = $this->PostsView->render('blog/posts.tpl.php'); // gets rendered content from view
      Output::setContent($content); // puts rendered content on Output class
      Output::showPage(); // sends headers and prints out generated content
    }
}
PHP:
<?php
class PostsModel extends AModel
{
    protected $data=array(); // an array that storages all data generated in model

    protected function addData($key,$value)
    {
        $this->data[$key]=$value;
    }

    public function getData()
    {
        return $this->data;
    }

    public function getPosts()
    {
        $data=DB::select('[date]','[author]','[announce]','[detail]','[tags]')
            ->from('posts',)
            ->where('[userid]','=',':userid')
            ->andWhere('[username]','=',':username')
            ->orderByDESC('[date]')
            ->query(
                array(
                    'userid' => 1,
                    'username' => 'CoolKid',
                ))
            ->fetchAll();
        $this->addData('posts',$data); // adds 'posts' data to model`s storage
    }
}
то он отстал от жизни на 10 лет и не имеет права на существование?

Код выше - взят из головы
 
Последнее редактирование:

fixxxer

К.О.
Партнер клуба
Право на существование в общем-то имеет все, что работает, но в целом Вурдалак прав.

Вот так будет вменяемо:
PHP:
      $this->PostsModel->loadPosts();
      $this->PostsView->bind($this->PostsModel);
      return $this->PostViews->render('blogs/posts.tpl.php');
UPD: Хотя, совершенно непонятно, почему $this->, это все локально для action-а. Должно быть просто $PostsModel и $PostsView, инстанциируемые в action-е.
 
Последнее редактирование:

CoolKid

Новичок
PHP:
return $this->PostViews->render('blogs/posts.tpl.php');
это я так понимаю означает что негоже в контроллере напрямую заниматься выводом, надо возвратить значение, а уже выводить где-то на другом уровне.
PHP:
$this->PostsView->bind($this->PostsModel);
очень хотелось бы понять что именно делает эта строчка. Ну то что она связывает значения - это понятно, каким именно образом? Преобразует массив данных в объект? или что?

UPD: Хотя, совершенно непонятно, почему $this->, это все локально для action-а. Должно быть просто $PostsModel и $PostsView, инстанциируемые в action-е.
Инстанциирование и происходит, из контейнера извлекается экземпляр и выполняется метод

собственно
PHP:
$this->PostsModel->loadPosts();
равносильно

$this->getModel('PostsModel')->loadPosts();
или
Container::make('PostsModel')->loadPosts();
Просто мне нравится синтаксис $this->PostsModel->someAction(), лаконично и аккуратно
 
Сверху