За последние 24 часа нас посетили 18150 программистов и 1650 роботов. Сейчас ищут 1548 программистов ...

API форума

Тема в разделе "Решения, алгоритмы", создана пользователем Alost, 29 сен 2009.

  1. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    TheShock

    • * Не проще ли сразу взять Doctrine и не парится? :)
      * Как насчёт такого:
      PHP:
      1. <?php
      2. $sql = 'SELECT SQL_CALC_FOUND_ROWS tg.id, tg.category_id, tg.numratings, tg.ratingsum,
      3.    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,
      4.    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,
      5.    tic.leechers AS leechers, tic.seeders AS seeders, tic.downloaded AS downloaded, tg.original_name,
      6.    tic.year, CONCAT(IFNULL(tg.genre, ""), IFNULL(tg.genre2, ""), IFNULL(tg.genre3, "")) AS genre,
      7.    tic.season_part, tic.producer
      8. FROM torr_groups tg
      9. JOIN torr_groups_trans tgt ON tgt.id=tg.id AND tgt.lang_code="'.$lang.'"
      10. JOIN categories c ON c.id=tg.category_id
      11. JOIN categories_trans ct ON ct.id=c.id AND ct.lang_code="'.$lang.'"
      12. LEFT JOIN (SELECT
      13.                 ti.torr_group_id,
      14.                 count(*) as cnt,
      15.                 sum(ti.comments) as comm_sum,
      16.                 SUM(ti.seeders) AS seeders,
      17.                 SUM(ti.leechers) AS leechers,
      18.                 SUM(ti.downloaded) AS downloaded,
      19.                 ti.year,
      20.                 ti.season_part,
      21.                 tt.producer
      22.             FROM torr_info AS ti
      23.             LEFT JOIN torr_trans AS tt ON tt.id = ti.id AND tt.lang_code= "'.$lang.'"
      24.             WHERE ti.visible="yes"
      25.             GROUP BY ti.torr_group_id
      26.         ) tic ON tic.torr_group_id=tg.id '.
      27. $where.' ORDER BY '.$order_by.$limit;

    А вообще вы забиваетесь, что форум должен иметь API, причём он должен уметь работать с классом для работы с базой, который ему могут подсунуть. Для этого самое разумное сделать его как mysqli, что бы он был простым и без навоторов - не все захотят юзать поставляемый в комплекте класс для работы с базой - у них может быть что-то совсем своё, на чём завёрнута хренова туча всякого. Нужно дать возможность заменить этот адаптер, т.е. он не должен быть таким, что жёстко зашит и всё.
     
  2. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Вот именно.

    Я уже писал. Для конкретных задач оно либо не нужно, либо недостаточно.
    Вот так оно должно работать
    PHP:
    1. <?php
    2. $model->filter('id', function ($val) {return $val >5});
    3. echo $model;
    4. ?>
    Допиливай свою ORM до этого вида.
     
  3. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    И зачем? Что по твоей логике оно должно выдать?
     
  4. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    она мне не нравится. я изучал её.

    Не вижу совершенно ничего тяжёлого в этом запросе.

    у тебя, кстати, тут ошибка

    Если бы мне требовалось реализовать подзапросы, я б их реализовал бы и выглядело б это так в итоге:

    PHP:
    1. <?php
    2. $db->query()
    3. ->select('*')
    4. ->from('groups', 'tg')
    5. ->join('groups_trans', 'tgt', 'id')
    6. ->join('categories', 'c', 'c.id = tg.category_id')
    7. ->join('categories_trans', 'ct', 'id')
    8. ->leftJoin(
    9.     $db->query()
    10.     ->select(array(
    11.         'ti.torr_group_id', 'ti.year', 'ti.season_part', 'tt.producer',
    12.         'count(*)'           => 'cnt',
    13.         'sum(ti.comments)'   => 'comm_sum',
    14.         'sum(ti.seeders)'    => 'seeders',
    15.         'sum(ti.leechers)'   => 'leechers',
    16.         'sum(ti.downloaded)' => 'downloaded'
    17.     ))
    18.     ->from('info', 'ti')
    19.     ->leftJoin('trans', 'tt', 'id')
    20.     ->where('ti.visible = "yes"')
    21.     ->where('lang_code = "%d"', $lang)
    22.     ->group('ti.torr_group_id'),
    23. 'tic', 'id')
    24. ->where(
    25.     array('tgt.langcode = %d', $lang),
    26.     array('ct.langcode = %d', $lang)
    27. );
    Вполне неплохо, правда? Может, даже, понятнее, чем native-sql

    и что оно должно дать? все элементы, у которых АйДи выше пяти? Ты извращенец. Это капец просто, а не пример хорошего кода.

    странный вопрос. ты не читаешь, что пишут другие посетители, а тупо споришь? по-моему там всё понятно до каждой буквы, а ты просто толстый
     
  5. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    TheShock
    А теперь усложним задачку с использованием FORCE INDEX и IGNORE INDEX :)

    Между прочим если ты не заметил, мне для многих полей alias ставить надо, т.е. select('*') там не в кассу. + там IF'ы, DATE_FORMAT'ы и прочая лабудень. В итоге всёравно получится так-же, как и просто запрос. Только ещё нехилый overhead из-за обёртки будет.
     
  6. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    ну покажи пример, что-ли. :)

    и зачем? ну хотя да, без моего билдера — алиасы вещь нужная

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

    PHP:
    1. <?php
    2. public function getGenre () {
    3.   return $this->genre . $this->genre2 . $this->genre3;
    4. }
    5.  
    6. public function isNew () {
    7.   return ($this->lastChanges > (time() - 86400));
    8. }
     
  7. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Вполне в вашем стиле - хреново.
    Ты перелицовываешь декларативный язык под императивный. Зачем?

    Кстати, что у тебя возвращает $db->query()? Что ты его так жизнерадостно засунул в leftJoin().... который хочет 3 параметра (нет, мы конечно можем перегрузить его, но...)

    ORM? - абстрагирование от БД как таковой.
    Тебя не должно беспокоить, кто, как, куда и откуда. Применение нативных языковых конструкций для получения результата. Когда ты и знать не будешь ни о каком SQL и его подобиях вида ваших любимых билдеров, которые рождаются из осознания недостатков ORM и избытка лени при недостатке опыта.

    Тебе все равно придется писать запросы. Билдер не ускорит их написание.
    Он может помочь автоматизировать (писать их без твоего участия) - но это ОRM, от которого ты только что сбежал. Поэтому пиши нативные SQL запросы - этого достаточно.

    Мальчик, а где там был пример хорошего кода?
    Речь шла о функциональности и методике.

    Адекватный вопрос. Ты привел неочевидный пример.

    Там ничерта не понятно. С какого бодуна ты решил так проджойнить таблицу саму на себя.
    Это особо одаренное применение NestedSets для форума? Или что?

    PHP:
    1.    ->leftJoin('posts', 'FirstPost', 'FirstPost.ID = Topic.FirstPostID')
    2.    ->leftJoin('users', 'FirstUser', 'FirstUser.ID = FirstPost.AuthorID')
    3.    ->leftJoin('posts', 'LastPost' , ' LastPost.ID = Topic.LastPostID')
    4.    ->leftJoin('users', 'LastUser' , ' LastUser.ID = LastPost.AuthorID')
    Какой результат ты получишь после этой хрени?
     
  8. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    С этим надо столкнуться :( Пока их не ударит граблями раз "надцать" по лбу - они не поймут.

    Впрочем некоторые не поймут никогда.
     
  9. Kreker

    Kreker Старожил

    С нами с:
    8 апр 2007
    Сообщения:
    5.433
    Симпатии:
    0
    Я вот тоже не пойму, какой смысл в этом? Строка практически идентична строке LEFT JOIN FirstUser ON FirstUser.ID = FirstPost.AuthorID.
    Если делать промежуточный слой между базой и безнес-логикой, то делать его нужно так, чтобы он никак не напоминал обычный SQL вперемешку с вызовами методов, чтобы любой программист, не зная SQL (ситуационно), смог получить данные без каких-либо проблем. В противном случае все эти билдеры теряют смысл.


    В общем хотели сделать API форума, а остановились на классе работы с СУБД. Вспоминается фраза 440hz про то, что кроме объекта СУБД есть и другие.
     
  10. [vs]

    [vs] Суперстар
    Команда форума Модератор

    С нами с:
    27 сен 2007
    Сообщения:
    10.559
    Симпатии:
    632
    Вот и я о том же. В API форума не должно быть ничего для работы с базой данных. Внутри - может быть, но не доступно для пользователя API, потому что это API форума. Форум - это законченое решение. API нужно для управления форумом. Иначе это будет не форум, а CMF =)
     
  11. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    *зевнул* ты не читаешь мои сообщения, а мельком проглядываешь. если бы перед тем, как умничать ты внимательно посмотрел бы моё сообщение, то заметил, что там всё правильно и в лефтДжоин передается три параметра.

    это еще одно доказательство того, что тебе лень думать. там вполне понятно всё. совершенно левые люди понимают:
    одна таблица джоинится по Topic.FirstPostID, а вторая — по Topic.LastPostID, то есть это первый и последний пост.

    в общем, я принципиально теперь буду игнорировать твои сообщения — ибо толстый тролль.

    Если хочешь высказаться по теме, то необходимо прочитать всю тему, а не только последний пост. Я уже не один раз говорил, зачем этот билдер
     
  12. TheShock

    TheShock Активный пользователь

    С нами с:
    30 май 2009
    Сообщения:
    1.255
    Симпатии:
    0
    Адрес:
    Київ
    Дык, [vs], никто и не говорит ведь, что надо заставлять пользователя юзать билдер. Мы обсуждали возможность сделать кроссбазность. А передавать достаточно так:
    new ForumApi (array(
    'Connection' => $resource
    ));
    Хотя, судя по тому, что автор молчит — апи никто писать не собирается
     
  13. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Кроссбазность - миф.
    Мне, пожалуйста, кроссбазность такой pg конструкции: select * from test1 where field1 && '{1,2}';
     
  14. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    В общем, SQL билдер - вещь удобная. И не во всяких абстракциях и короссбазностях дело. И уж точно этот клас не должен выдавать объекты - это работа уровня выше.
    В моем случае SQL билдер позволяет скрыть реализацию фильтрации полей и пре/пост конвертации данных. Т.е. зная, что поле типа массив - на выходе будет массив, и т.д. Объект тупо пихает все свои свойства к конструктор, а тот уже отсекает неизвестные поля, оставля нужное. Плюс, возможность дебагинга, внутреннего профайлинга и т.д.
    Обратная сторона этой мелали - говнокласс. Начиналось то все красиво - сейчас этот билдер - один из самых больших и сложных к понимаю классов в большой системе - отсюда огромная стоимость модификаци и дебага самого класса.
    И это притом, что он осуществляет лишь выдачу данных, а обертка в объекты - это уже отдельная тема датамапера.
    Хотя, многие функции, что убраны в класс билдера - все равно нужны, и если унести генерацию SQL в сам датамапер - это его сильно усложнит (а он и так не прост, опять же).
    В общем, SQL билдер - палка о двух концах и без опыта к нему подходить опасно - можно не успеть вовремя остановиться.
     
  15. Mr.M.I.T.

    Mr.M.I.T. Старожил

    С нами с:
    28 янв 2008
    Сообщения:
    4.586
    Симпатии:
    1
    Адрес:
    у тебя канфетка?
    TheShock
    это называется найди 3 отличия от шаблонного форума
    на счёт билдеров, считаю их по большей части - хернёй. кроссБД они всё равно полностью не поддерживают. Голосую за "драйвера" =)
     
  16. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Еще один понимающий.

    Фу, показываю на кошечках, как для блондинок
    Код (Text):
    1. <?php
    2. ->leftJoin('users', 'FirstUser', 'FirstUser.ID = FirstPost.AuthorID')
    3. ?>
    Строка, строка, строка

    Код (Text):
    1. <?php
    2. ->leftJoin(
    3.      $db->query()
    4.      ->select(array(
    5.          'ti.torr_group_id', 'ti.year', 'ti.season_part', 'tt.producer',
    6.          'count(*)'          => 'cnt',
    7.          'sum(ti.comments)'  => 'comm_sum',
    8.          'sum(ti.seeders)'    => 'seeders',
    9.          'sum(ti.leechers)'  => 'leechers',
    10.          'sum(ti.downloaded)' => 'downloaded'
    11.      ))
    12.      ->from('info', 'ti')
    13.      ->leftJoin('trans', 'tt', 'id')
    14.      ->where('ti.visible = "yes"')
    15.      ->where('lang_code = "%d"', $lang)
    16.      ->group('ti.torr_group_id'),
    17.  'tic', 'id')
    18. ?>
    хрень, строка, строка

    Поэтому повторюсь, для альтернативноодаренных. Что оно у тебя возвращает?

    Чудно, теперь резонный вопрос, а зачем? Какой в этом смысл?
    Показать как круто билдер вывел ненужную ерунду? :)

    Если бы ты внимательно читал (ага, в своем глазу и бревна не видать)
    то увидел бы...
    В случае с форумом у тебя будет ограниченное (и достаточно маленькое) число видов запросов.

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

    Да ради бога :) "Баба з возу - кобилi легше".
    Свою принципиальную неспособность оценить проблемму, необходимость ее решения и сложность ты отлично уже показал.
    Может и придет это к тебе с опытом. А может и не придет :)
     
  17. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Я бы предпочел это делать на этапах ввода вывода.

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

    Но это уже больше дело подхода. Domain objects или нет.
     
  18. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Simpliest, мы думали о том, что бы объект модели четко привязывался к тем полям, что есть в соответствующей таблице и ее типу. Тогда объект при сохранении сможет сам понять в какую именно строку преобразовать массив, к примеру - в сериализованный массив, в массив формата постгрес (если поле типа массива) и т.д.
    Но немного ненравится избыточность информации - и в модели и в базе. Ну и билдер стартовал раньше этих мыслей. Если бы делали все заново... не знаю, возможно и правда учили модели фильтровать свои данные на сохранении беря метадату из базы.
     
  19. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Черт его знает :)

    Я в масштабах одной задачи на классе доступа к БД (10к строк - 350кб) прошел все 3 этапа.
    1. Одно действие - один конкретный запрос.
    2. Билдер запросов.
    3. Dataset.

    В итоге выводы я уже приводил.

    Если объекты/таблицы стандартны - то Dataset (ActiveRecord, ORM) самая удобная и легкая вещь (типичные формы создавались за 10-15 минут с уже готовым интерфейсом)
    Если объекты/таблицы нестандартны (несут отпечаток тяжелого детства - legacy код) - то п.1. наиболее оптимален, хотя по-неопытности и кажется что работы больше.

    Полноценному Билдеру нашлось место только в п.3.
    Во всех остальных случаях пара специфичных (для приложения) методов
    типа
    PHP:
    1. <?php
    2.     /**
    3.      * Returns limit clause for Warehouse and Company
    4.      *
    5.      * @param ineger  $mode = 0 - all limits (default)| 1 - no warehouse limit| 2 - no company limit
    6.      * @return string
    7.      */
    8.     public function getWarehouseAndCompanyLimit($mode = 0) {}
    9.  
    10. // возвращала нечто вроде
    11. echo "WHERE 1=1 AND WH_ID = 'SOMEID' AND COMPANY_ID IN ('COMPANY1','COMPANY2') AND ";
    12.  
    13. ?>
    14.  
    оказалась наиболее удобной и простой в поддержке.
     
  20. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    А она нужна, ведь мы маппим разные вещи друг на друга. Т.е. это не избыточность, а таблица маппинга (соответствия)
    типов, структур и т.д.

    Вот пример бесполезной реализации 2хлетней давности.
    Field
    Record
    Dataset
    getDataset
     
  21. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    С чего-то разные. Модель есть представление таблицы, объект этой модели - представление конкретной записи.
    Все это по сути ORM, и где в реализации ORM будет сидеть фильтрация данных - это уже второй вопрос, но довольно принципиальный.

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

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

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Т.е. вот два варианта, говорим "модель->save()"
    Без билдера: модель-save() { берем данные из $this, специфичные для модель проверки, специфичные для базы проверки, генерим UPDATE } ---> $db->query()
    С билдером: модель-save() { берем данные из $this, специфичные для модель проверки, генерим некую структуру для билдера } ----> $билдер { специфичные для базы проверки, генерим UPDATE } ----> $db->query();
    Вроде первая схема то и выглядит проще - меньше вызовов, но ведь не это основной принцип красивого кода, правда? Вызовов меньше, зато больше разношерстного кода в модели и больше вероятность повторов, если потребуется работать с базой в других местах (датамапер).
    Вторая схема выглядит сложнее... да и реализуется нелегко... зато обеспечивает легкое реюзание кода "специфичные для базы проверки".
     
  23. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Вот в этом у нас расхождение.

    Для меня модель это не столько представление таблицы. Сколько некая сущность приложения.

    Т.е. условно говоря 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/
    хотя заметка откровенно слабовата. Но в комменатриях были достаточно ценные вещи.
     
  24. MiksIr

    MiksIr Активный пользователь

    С нами с:
    29 ноя 2006
    Сообщения:
    2.339
    Симпатии:
    44
    Некий класс Topic может что угодно =)
    А модель топик - это модель топика, а сообщения там уже - модели сообщений. Конечно, объект топика может содержать ссылки на все модели сообщений. Ели по каким-то причинам данные в базе, относящиееся именно к одной модели, лежат в нескольких таблицах - можно просто сделать вьюху в базе.
     
  25. Simpliest

    Simpliest Активный пользователь

    С нами с:
    24 сен 2009
    Сообщения:
    4.511
    Симпатии:
    2
    Адрес:
    Донецк
    Почти единомышленник :)

    Только называем мы некоторые вещи по-разному.
    Билдер тут не ключевой :) Основной именно класс маппинга. А уж будет ли он строить запрос или использовать raw SQL написанный программистом - это не сильно важно.
    Просто для типичных ситуаций билдер будет удобен, если ситуации будут разнообразны - выигрыша ноль, зато имеем оверхед в поддержке.

    Но вся эта схема очень близка к ОРМ, реально ближе ее нет ничего. (может это и есть ОРМ в конечном итоге :) )