TheShock * Не проще ли сразу взять Doctrine и не парится? * Как насчёт такого: PHP: <?php $sql = 'SELECT SQL_CALC_FOUND_ROWS tg.id, tg.category_id, tg.numratings, tg.ratingsum, DATE_FORMAT(tg.last_changes, "%Y-%m-%d %H:%i:%s") AS tg_last_changes, IF(tg.last_changes > NOW() - INTERVAL 1 DAY, 1, 0) AS is_new, tgt.name AS tgt_name, c.image, c.activity, ct.name AS ct_name, tic.cnt AS torr_cnt, tic.comm_sum AS comm_sum, tic.leechers AS leechers, tic.seeders AS seeders, tic.downloaded AS downloaded, tg.original_name, tic.year, CONCAT(IFNULL(tg.genre, ""), IFNULL(tg.genre2, ""), IFNULL(tg.genre3, "")) AS genre, tic.season_part, tic.producer FROM torr_groups tg JOIN torr_groups_trans tgt ON tgt.id=tg.id AND tgt.lang_code="'.$lang.'" JOIN categories c ON c.id=tg.category_id JOIN categories_trans ct ON ct.id=c.id AND ct.lang_code="'.$lang.'" LEFT JOIN (SELECT ti.torr_group_id, count(*) as cnt, sum(ti.comments) as comm_sum, SUM(ti.seeders) AS seeders, SUM(ti.leechers) AS leechers, SUM(ti.downloaded) AS downloaded, ti.year, ti.season_part, tt.producer FROM torr_info AS ti LEFT JOIN torr_trans AS tt ON tt.id = ti.id AND tt.lang_code= "'.$lang.'" WHERE ti.visible="yes" GROUP BY ti.torr_group_id ) tic ON tic.torr_group_id=tg.id '. $where.' ORDER BY '.$order_by.$limit; А вообще вы забиваетесь, что форум должен иметь API, причём он должен уметь работать с классом для работы с базой, который ему могут подсунуть. Для этого самое разумное сделать его как mysqli, что бы он был простым и без навоторов - не все захотят юзать поставляемый в комплекте класс для работы с базой - у них может быть что-то совсем своё, на чём завёрнута хренова туча всякого. Нужно дать возможность заменить этот адаптер, т.е. он не должен быть таким, что жёстко зашит и всё.
Вот именно. Я уже писал. Для конкретных задач оно либо не нужно, либо недостаточно. Вот так оно должно работать PHP: <?php $model->filter('id', function ($val) {return $val >5}); echo $model; ?> Допиливай свою ORM до этого вида.
она мне не нравится. я изучал её. Не вижу совершенно ничего тяжёлого в этом запросе. у тебя, кстати, тут ошибка Если бы мне требовалось реализовать подзапросы, я б их реализовал бы и выглядело б это так в итоге: PHP: <?php $db->query() ->select('*') ->from('groups', 'tg') ->join('groups_trans', 'tgt', 'id') ->join('categories', 'c', 'c.id = tg.category_id') ->join('categories_trans', 'ct', 'id') ->leftJoin( $db->query() ->select(array( 'ti.torr_group_id', 'ti.year', 'ti.season_part', 'tt.producer', 'count(*)' => 'cnt', 'sum(ti.comments)' => 'comm_sum', 'sum(ti.seeders)' => 'seeders', 'sum(ti.leechers)' => 'leechers', 'sum(ti.downloaded)' => 'downloaded' )) ->from('info', 'ti') ->leftJoin('trans', 'tt', 'id') ->where('ti.visible = "yes"') ->where('lang_code = "%d"', $lang) ->group('ti.torr_group_id'), 'tic', 'id') ->where( array('tgt.langcode = %d', $lang), array('ct.langcode = %d', $lang) ); Вполне неплохо, правда? Может, даже, понятнее, чем native-sql и что оно должно дать? все элементы, у которых АйДи выше пяти? Ты извращенец. Это капец просто, а не пример хорошего кода. странный вопрос. ты не читаешь, что пишут другие посетители, а тупо споришь? по-моему там всё понятно до каждой буквы, а ты просто толстый
TheShock А теперь усложним задачку с использованием FORCE INDEX и IGNORE INDEX Между прочим если ты не заметил, мне для многих полей alias ставить надо, т.е. select('*') там не в кассу. + там IF'ы, DATE_FORMAT'ы и прочая лабудень. В итоге всёравно получится так-же, как и просто запрос. Только ещё нехилый overhead из-за обёртки будет.
ну покажи пример, что-ли. и зачем? ну хотя да, без моего билдера — алиасы вещь нужная я заметил. специально не переписывал, чтобы указать на то, что я считаю данный подход неверным — логику приложения выносить в запросы. это всё должны быть методы соответствующих классов. ужас. Вот более правильный подход: PHP: <?php public function getGenre () { return $this->genre . $this->genre2 . $this->genre3; } public function isNew () { return ($this->lastChanges > (time() - 86400)); }
Вполне в вашем стиле - хреново. Ты перелицовываешь декларативный язык под императивный. Зачем? Кстати, что у тебя возвращает $db->query()? Что ты его так жизнерадостно засунул в leftJoin().... который хочет 3 параметра (нет, мы конечно можем перегрузить его, но...) ORM? - абстрагирование от БД как таковой. Тебя не должно беспокоить, кто, как, куда и откуда. Применение нативных языковых конструкций для получения результата. Когда ты и знать не будешь ни о каком SQL и его подобиях вида ваших любимых билдеров, которые рождаются из осознания недостатков ORM и избытка лени при недостатке опыта. Тебе все равно придется писать запросы. Билдер не ускорит их написание. Он может помочь автоматизировать (писать их без твоего участия) - но это ОRM, от которого ты только что сбежал. Поэтому пиши нативные SQL запросы - этого достаточно. Мальчик, а где там был пример хорошего кода? Речь шла о функциональности и методике. Адекватный вопрос. Ты привел неочевидный пример. Там ничерта не понятно. С какого бодуна ты решил так проджойнить таблицу саму на себя. Это особо одаренное применение NestedSets для форума? Или что? PHP: ->leftJoin('posts', 'FirstPost', 'FirstPost.ID = Topic.FirstPostID') ->leftJoin('users', 'FirstUser', 'FirstUser.ID = FirstPost.AuthorID') ->leftJoin('posts', 'LastPost' , ' LastPost.ID = Topic.LastPostID') ->leftJoin('users', 'LastUser' , ' LastUser.ID = LastPost.AuthorID') Какой результат ты получишь после этой хрени?
С этим надо столкнуться Пока их не ударит граблями раз "надцать" по лбу - они не поймут. Впрочем некоторые не поймут никогда.
Я вот тоже не пойму, какой смысл в этом? Строка практически идентична строке LEFT JOIN FirstUser ON FirstUser.ID = FirstPost.AuthorID. Если делать промежуточный слой между базой и безнес-логикой, то делать его нужно так, чтобы он никак не напоминал обычный SQL вперемешку с вызовами методов, чтобы любой программист, не зная SQL (ситуационно), смог получить данные без каких-либо проблем. В противном случае все эти билдеры теряют смысл. В общем хотели сделать API форума, а остановились на классе работы с СУБД. Вспоминается фраза 440hz про то, что кроме объекта СУБД есть и другие.
Вот и я о том же. В API форума не должно быть ничего для работы с базой данных. Внутри - может быть, но не доступно для пользователя API, потому что это API форума. Форум - это законченое решение. API нужно для управления форумом. Иначе это будет не форум, а CMF =)
*зевнул* ты не читаешь мои сообщения, а мельком проглядываешь. если бы перед тем, как умничать ты внимательно посмотрел бы моё сообщение, то заметил, что там всё правильно и в лефтДжоин передается три параметра. это еще одно доказательство того, что тебе лень думать. там вполне понятно всё. совершенно левые люди понимают: одна таблица джоинится по Topic.FirstPostID, а вторая — по Topic.LastPostID, то есть это первый и последний пост. в общем, я принципиально теперь буду игнорировать твои сообщения — ибо толстый тролль. Если хочешь высказаться по теме, то необходимо прочитать всю тему, а не только последний пост. Я уже не один раз говорил, зачем этот билдер
Дык, [vs], никто и не говорит ведь, что надо заставлять пользователя юзать билдер. Мы обсуждали возможность сделать кроссбазность. А передавать достаточно так: new ForumApi (array( 'Connection' => $resource )); Хотя, судя по тому, что автор молчит — апи никто писать не собирается
Кроссбазность - миф. Мне, пожалуйста, кроссбазность такой pg конструкции: select * from test1 where field1 && '{1,2}';
В общем, SQL билдер - вещь удобная. И не во всяких абстракциях и короссбазностях дело. И уж точно этот клас не должен выдавать объекты - это работа уровня выше. В моем случае SQL билдер позволяет скрыть реализацию фильтрации полей и пре/пост конвертации данных. Т.е. зная, что поле типа массив - на выходе будет массив, и т.д. Объект тупо пихает все свои свойства к конструктор, а тот уже отсекает неизвестные поля, оставля нужное. Плюс, возможность дебагинга, внутреннего профайлинга и т.д. Обратная сторона этой мелали - говнокласс. Начиналось то все красиво - сейчас этот билдер - один из самых больших и сложных к понимаю классов в большой системе - отсюда огромная стоимость модификаци и дебага самого класса. И это притом, что он осуществляет лишь выдачу данных, а обертка в объекты - это уже отдельная тема датамапера. Хотя, многие функции, что убраны в класс билдера - все равно нужны, и если унести генерацию SQL в сам датамапер - это его сильно усложнит (а он и так не прост, опять же). В общем, SQL билдер - палка о двух концах и без опыта к нему подходить опасно - можно не успеть вовремя остановиться.
TheShock это называется найди 3 отличия от шаблонного форума на счёт билдеров, считаю их по большей части - хернёй. кроссБД они всё равно полностью не поддерживают. Голосую за "драйвера" =)
Еще один понимающий. Фу, показываю на кошечках, как для блондинок Код (Text): <?php ->leftJoin('users', 'FirstUser', 'FirstUser.ID = FirstPost.AuthorID') ?> Строка, строка, строка Код (Text): <?php ->leftJoin( $db->query() ->select(array( 'ti.torr_group_id', 'ti.year', 'ti.season_part', 'tt.producer', 'count(*)' => 'cnt', 'sum(ti.comments)' => 'comm_sum', 'sum(ti.seeders)' => 'seeders', 'sum(ti.leechers)' => 'leechers', 'sum(ti.downloaded)' => 'downloaded' )) ->from('info', 'ti') ->leftJoin('trans', 'tt', 'id') ->where('ti.visible = "yes"') ->where('lang_code = "%d"', $lang) ->group('ti.torr_group_id'), 'tic', 'id') ?> хрень, строка, строка Поэтому повторюсь, для альтернативноодаренных. Что оно у тебя возвращает? Чудно, теперь резонный вопрос, а зачем? Какой в этом смысл? Показать как круто билдер вывел ненужную ерунду? Если бы ты внимательно читал (ага, в своем глазу и бревна не видать) то увидел бы... В случае с форумом у тебя будет ограниченное (и достаточно маленькое) число видов запросов. Самыми сложносоставными из которых будут запросы для функций поиска (пользователей, сообщений). Да ради бога "Баба з возу - кобилi легше". Свою принципиальную неспособность оценить проблемму, необходимость ее решения и сложность ты отлично уже показал. Может и придет это к тебе с опытом. А может и не придет
Я бы предпочел это делать на этапах ввода вывода. Внутри приложения бегают легкие-простые траспортные классы (только свойства и возможно Iterator, ArrayAccess etc) или даже просто ассоциативные массивы. Но это уже больше дело подхода. Domain objects или нет.
Simpliest, мы думали о том, что бы объект модели четко привязывался к тем полям, что есть в соответствующей таблице и ее типу. Тогда объект при сохранении сможет сам понять в какую именно строку преобразовать массив, к примеру - в сериализованный массив, в массив формата постгрес (если поле типа массива) и т.д. Но немного ненравится избыточность информации - и в модели и в базе. Ну и билдер стартовал раньше этих мыслей. Если бы делали все заново... не знаю, возможно и правда учили модели фильтровать свои данные на сохранении беря метадату из базы.
Черт его знает Я в масштабах одной задачи на классе доступа к БД (10к строк - 350кб) прошел все 3 этапа. 1. Одно действие - один конкретный запрос. 2. Билдер запросов. 3. Dataset. В итоге выводы я уже приводил. Если объекты/таблицы стандартны - то Dataset (ActiveRecord, ORM) самая удобная и легкая вещь (типичные формы создавались за 10-15 минут с уже готовым интерфейсом) Если объекты/таблицы нестандартны (несут отпечаток тяжелого детства - legacy код) - то п.1. наиболее оптимален, хотя по-неопытности и кажется что работы больше. Полноценному Билдеру нашлось место только в п.3. Во всех остальных случаях пара специфичных (для приложения) методов типа PHP: <?php /** * Returns limit clause for Warehouse and Company * * @param ineger $mode = 0 - all limits (default)| 1 - no warehouse limit| 2 - no company limit * @return string */ public function getWarehouseAndCompanyLimit($mode = 0) {} // возвращала нечто вроде echo "WHERE 1=1 AND WH_ID = 'SOMEID' AND COMPANY_ID IN ('COMPANY1','COMPANY2') AND "; ?> оказалась наиболее удобной и простой в поддержке.
А она нужна, ведь мы маппим разные вещи друг на друга. Т.е. это не избыточность, а таблица маппинга (соответствия) типов, структур и т.д. Вот пример бесполезной реализации 2хлетней давности. Field Record Dataset getDataset
С чего-то разные. Модель есть представление таблицы, объект этой модели - представление конкретной записи. Все это по сути ORM, и где в реализации ORM будет сидеть фильтрация данных - это уже второй вопрос, но довольно принципиальный. Просто если отказываться от билдера, заменяя его на обычную прослойку с исполнением обычного SQL - то многие вещи специфичные для базы (такие, как сериализация в том или ином формате) оказываются в классе модели, или в классе датамапера - так как эти проверки нужно сделать еще до построения SQL, что уже не есть хорошо. Плюс, туда же сажаются всякие специфичные для базы проверки - я приводил пример, данные модели формата "массив" можно хранить в базе несколькими способами, что напрямую зависит от типа данных поля базы. Т.е., получается, мы в моделе с одной стороны работаем с абстрактными данными, с другой стороны - там куча специфичной для базы информации.
Т.е. вот два варианта, говорим "модель->save()" Без билдера: модель-save() { берем данные из $this, специфичные для модель проверки, специфичные для базы проверки, генерим UPDATE } ---> $db->query() С билдером: модель-save() { берем данные из $this, специфичные для модель проверки, генерим некую структуру для билдера } ----> $билдер { специфичные для базы проверки, генерим UPDATE } ----> $db->query(); Вроде первая схема то и выглядит проще - меньше вызовов, но ведь не это основной принцип красивого кода, правда? Вызовов меньше, зато больше разношерстного кода в модели и больше вероятность повторов, если потребуется работать с базой в других местах (датамапер). Вторая схема выглядит сложнее... да и реализуется нелегко... зато обеспечивает легкое реюзание кода "специфичные для базы проверки".
Вот в этом у нас расхождение. Для меня модель это не столько представление таблицы. Сколько некая сущность приложения. Т.е. условно говоря class Topic может хранится не в одной таблице, а быть размазанным по нескольким Поскольку может включать в себя еще и коллекцию сообщений/комментариев. Последнее время стараюсь использовать именно такой подход с маппером. Это кстати на мой взгляд более удобно, хотя модель и получается как бы раздерганной на несколько классов. Есть Модель. (class Sample {}) Есть простой транспортный класс. (class SampleResult extends Result {}) Есть датамаппер, который маппит результаты отдаваемые БД в поля транспортного класса. (class SampeMapper extends Mapper {}) При этом вся работа идет через Модель. Она содержит все методы специфичные для приложения и использует данные из SampleResult. А с базой общается SampleMapper. Вот примерно к такому я склоняюсь в последнее время. http://www.slideshare.net/weierophinney ... ts-1766001 Ну и отчасти вот http://habrahabr.ru/blogs/refactoring/67036/ хотя заметка откровенно слабовата. Но в комменатриях были достаточно ценные вещи.
Некий класс Topic может что угодно =) А модель топик - это модель топика, а сообщения там уже - модели сообщений. Конечно, объект топика может содержать ссылки на все модели сообщений. Ели по каким-то причинам данные в базе, относящиееся именно к одной модели, лежат в нескольких таблицах - можно просто сделать вьюху в базе.
Почти единомышленник Только называем мы некоторые вещи по-разному. Билдер тут не ключевой Основной именно класс маппинга. А уж будет ли он строить запрос или использовать raw SQL написанный программистом - это не сильно важно. Просто для типичных ситуаций билдер будет удобен, если ситуации будут разнообразны - выигрыша ноль, зато имеем оверхед в поддержке. Но вся эта схема очень близка к ОРМ, реально ближе ее нет ничего. (может это и есть ОРМ в конечном итоге )