Xpoint
   [напомнить пароль]

ORM, методы создания объектов из СУБД, ООП

Метки: [без меток]
2009-07-10 15:42:01 [обр] triumvurat[досье]

Мне очень нравится ОО-стиль, но ООП очень не вяжется с реляционными СУБД.

Если я правильно понимаю, ORM - эта такая штука, которая транслирует реляционное представление данных из базы в объекты.
Я хочу такую штуку, ибо хочется писать в ОО стиле, а реляционная база мне все портит своей реляционностью.

Но сколько бы я не начинал писать (предпринимал попытки) ОРМ-подобные системы, я всегда наталкиваюсь на то, что сложные запросы всегда приходится писать как есть. В итоге получается абсолютно неадекватная каша из объектов и массивов.

Давайте погорим, кто и как решает проблемы подобного рода. Меня интересуют ваши удачные решения, пожалуйста, не отсылайте к ORM фрейморкам и системам типа Doctrine.

спустя 2 часа 23 минуты [обр] Алексей В. Иванов(509/2861)[досье]
ORM это замечательно, но не старайтесь избавиться от SQL-запросов — это никому не нужная крайность.
спустя 8 минут [обр] Давид Мзареулян(536/1003)[досье]

Я пока (!) для себя (!) остановился на простенькой самописной библиотечке, которая умеет мапить результат выборки из БД на классы моей модели. При этом банальности User::getById($userid) она, конечно, умеет сама, а когда надо выбрать несколько объектов по сложному критерию — то пищу SQL (типа User::getListOf("select ...")). Объекты также умеют следить за присовениями своих БД-полей и при вызове $user->save() сбрасывать изменения в базу.

Как-то так. В принципе, мне пока хватает…

спустя 4 часа 22 минуты [обр] Прокаев2(13/35)[досье]
Давид, не поделитесь ?
спустя 23 минуты [обр] Василий Свиридов(53/175)[досье]

Если конвенции именования в базе достаточно строгие, можно написать простой ORM достаточно быстро. Особенно на PHP, т.к. $$ __get(), __set() и все дела.

Например у нас каждое поле в таблице имеет свой префикс из трех букв. И все ключи будут называться <prefix>Id

Например есть

tblCompany
comId (PK)
comName
comAddress

и

tblContact
conId (PK)
comId (FK)
conName
conPhone

Таким образом по префиксам легко понять из какой таблицы это поле достали, и с кем эта таблица связана. да и джойны вида
FROM tblContact con JOIN tblCompany com ON con.comId = com.comId намного легче писать.

Таким образом в ОРМ, если использован свой запрос с джойнами - по префиксам колонок можно разрулить к какому объекту данное поле принадлежит. И заполнять их соответственно. И если ID не присутствует - делать объект только для чтения.

спустя 3 часа 51 минуту [обр] Ali(5/5)[досье]
И все же, чем не угодили Propel и Doctrine и еще туча готовых (в том числе легких) ORM?
Как по мне, это больше похоже на изобретение своего велосипеда. Разве что полезно в образовательных целях...
спустя 11 часов [обр] Давид Мзареулян(536/1003)[досье]

Ali[досье] А Вы знаете ЛЁГКИЕ ORM? Пропель — мегамонстр с генерацией кода…

Прокаев2[досье] «Поделившись», придётся это всё докментировать и поддерживать. А я не хочу. Оно пишется за день.

спустя 1 час 17 минут [обр] Ali(5/5)[досье]

Навскидку попались такие:
http://phplightorm.wiki.sourceforge.net/
http://www.outlet-orm.org/site/
http://www.getdorm.com/
http://www.ohloh.net/p/php-orm-lite
http://code.google.com/p/php-orm/
http://www.php-rocks.com/rocks_php_library.php
http://www.coughphp.com/

Не вникал в подробности, но в любом случае я думаю можно найти себе по душе...

спустя 6 минут [обр] Давид Мзареулян(536/1003)[досье]
Ali[досье] Навскидку-то имя им — легион. Вопрос в том, что из этого реально можно использовать.
спустя 9 часов [обр] Thirteensmay(17/157)[досье]
triumvurat[досье] Ну собственно народ вас к идее неотказа от SQL уже подвел, я тоже это поддерживаю, если система у вас нормально модульно построена то не вижу чем таким SQL вам может попортить весь ООП. И ненадо пытаться реализовать на ООП функциональность SQL, как и многие другие вещи, не ООП'а это дело.
спустя 1 день 11 часов [обр] Прокаев2(13/35)[досье]
Давид Мзареулян[досье] я тоже писал сам (просмотрев несколько готовых библиотек)
спустя 1 час 54 минуты [обр] triumvurat[досье]

Алексей В. Иванов[досье]

но не старайтесь избавиться от SQL-запросов — это никому не нужная крайность.

Я не стараюсь избавиться от запросов, я стараюсь писать в ОО-стиле.

Давид Мзареулян[досье]

то пищу SQL (типа User::getListOf("select ..."))

И что этот метод возвращает? Обычный результат типа массива или всё таки объект? Вот можно поподробнее с этим?

Давайте я расскажу про сове решение!

Первый мой паттерн, про который я прочитал, был меппер. Но если меппер призван разделить БД и её поля от кода, то мои мепперы были призваны получать по меньшей мере простые объекты. Т.е. пример:

// Создаем меппер пользователя
$mapper = new User_Mapper();

// получаем его по ID
$user = $mapper->findById(34);

User_Mapper "знает", какой класс фигурирует в роли модели пользователя. Выбрав данные из таблицы, User_Mapper создает модель и заполняет её получившимися результатами. По такому принципе у меня устроены все методы выборки простых объектов.

Все методы выборки принимают необязательный аргумент $params, который содержит критерии выборки:

$Article_Mapper = new Article_Mapper();

$params['where'] = '`article_main` = 1 AND `article_active` = 1';
$params['what'] = '`id`, `article_header`, `article_description`';
$params['limit'] = array('start'=>0, 'stop'=>10);

$article = $Article_Mapper->findByParams($params);

if ($article->id)
{
    echo 'Статья '$article->article_header.' имеет ID '.$article->id;
}

Также были реализованы методы типа

// получаем объект-список
$user_list = $mapper->getObjectList($params=array());

// новый пустой объект пользователя
$empty_user = $mapper->createNew();

// Создаем из POST-массива объект на основании полученных данных
$user = $mapper->createFromArray( $_POST['user'] );

Всё это работает, но до момента, когда начинается объединение, использование встроенных функций MySql и нетривиальные запросы. В таких случаях приходится писать методы в стиле

public function getRating($object)
{
    $res = $this->db->query('# куча нетривиального SQL-кода', $object->id);

    return $res->fetch_assoc();
}

что мне ОЧЕНЬ не нравится!! Вся ООПшность летит к чертям.

спустя 45 минут [обр] Thirteensmay(17/157)[досье]
Если не извращаться и не пытаться летать на луну на утках то ничего не летит. Говорю вам, не пытайтесь реализовать на ООП функциональность SQL, как и для многих других задач, ООП не подходит. ООП это вообще подход, а реляционка - модель, чувствуете разницу ? В лучшем случае сильно затрахаясь родите очередной кривой велосипед. ООП хорошо подходит для интерфейсов, вот и используйте его по назначению. Ненадо с помощью ООП пытаться работать с данными. Или возможно я вас не понимаю, что вы имеете ввиду под "я стараюсь писать в ОО-стиле" ? Вам не нравится что в коде ОО метода проскакивает не ОО ? ну так это всего лишь потому что вы вышли на границу, хотите спрячьте это, выделив в отдельный слой, хранимую процедуру, файл и т.п. От взаимодействия с не ОО вы всеравно никуда не денетесь.
спустя 55 минут [обр] Давид Мзареулян(536/1003)[досье]

triumvurat[досье]
> И что этот метод возвращает? Обычный результат типа массива или всё таки объект? Вот можно поподробнее с этим?

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

> Все методы выборки принимают необязательный аргумент $params, который содержит критерии выборки:

Небезопасно… получается, Вам надо параметры запроса прямо в SQL-код вставлять.

спустя 18 минут [обр] triumvurat[досье]

Давид Мзареулян[досье] Я так понимаю, что Вы пишите что-то типа

User::getListOf("select * from user where user.id < 23 AND user.name LIKE '%петя%'");

т.е. передаете SQL в качестве аргумента? Или же SQL зашит в методе User::getListOf.... ?

Небезопасно…

Почему же? На уровне БД есть класс, который слешит все, что нужно.

спустя 53 минуты [обр] Давид Мзареулян(536/1003)[досье]

Я пишу

User::getListOf("select * from user where id < ? AND name LIKE ?", array(23, '%петя%'));

Точнее,

User::getListOf("select {{*}} from {{table}} where id < ? AND name LIKE ?", array(23, '%петя%'));

, но это не существенно.

спустя 18 часов [обр] Филипп Ткачев(20/112)[досье]
А чем плох такой подход?
$users = $dbobj->sql2array("SELECT * FROM `user` WHERE id < $id AND name LIKE '%$username%'");
спустя 1 минуту [обр] triumvurat[досье]
Филипп Ткачев[досье] мы вообще то про ORM, причем тут sql2array?
спустя 27 минут [обр] triumvurat[досье]

Давид Мзареулян[досье] Ну хорошо. Реализовать метод типа

User::getListOf("select {{*}} from {{table}} where id < ? AND name LIKE ?", array(23, '%петя%'));

Легко. А что делать, если нужно сделать JOIN? Вот допустим в таблице пользователей есть поле ID_COUNTRY, хранящее ID страны проживания юзера. Вот нам надо сделать выборку типа

SELECT 
    user.*,
    countries.country_name 
FROM 
    user
INNER JOIN 
    countries
ON
    user.id_country = countries.id

Как Вы поступите в данном случае?

Вот в таких JOIN-ах я и затормозил. Модель пользователя содержит свойство id_country числового типа, соответственно присобачивать строку countries.country_name к модели не правильно. Тогда как получить countries.country_name для модели? Напрашивается решение написать метод модели User->getCountry() для получения объекта страны, но это дополнительный запрос. Жертвовать проиводительностью базы ради ООП? Или жертвовать ООП ради производителньости?

спустя 32 минуты [обр] Филипп Ткачев(20/112)[досье]
Получил массив, преобразовал его в коллекцию объектов.
GlobalObject->Users[0]->UserName
спустя 2 часа 59 минут [обр] Давид Мзареулян(536/1003)[досье]

triumvurat[досье] Это получается уже обсуждение моего ORM, вместо обсуждения ORM вообще:) А мой — он маленький и заточенный под мои нужды. Кому другому он может и не подойти.

Лично я делаю “User->getCountry()”. У меня просто очень редко бывают ситуации, когда какое-то внешнее поле надо постоянно тащить за объектом. Если всё-таки понадобится — скорее всего просто закэширую связанное значение.

спустя 58 минут [обр] Давид Мзареулян(536/1003)[досье]
UPD Но в принципе, ничто не мешает обучить модель брать отдельные поля из других таблиц при конструировании запроса. Это можно упрятать даже в имеющийся у меня простенький синтаксис select {{*}} from {{table}}. “{{*}}” — это на самом деле не «всё подряд», а те поля, которые определены в описании модели как автозагружаемые. Просто “{{table}}” заменяется на нужный join и т. д.
спустя 1 час 34 минуты [обр] Thirteensmay(17/157)[досье]
triumvurat[досье] Можно рассмотреть возможность добавления country_name не к модели (классу), а к объекту который возвращается методом создания объектов, т.е. брать базовый класс User, динамически расширять его дополнительными полями определяемыми SQL запросом, создавать и возвращать расширенный объект.
спустя 22 минуты [обр] triumvurat[досье]
ох.. геммор страшный всё это.
спустя 10 часов [обр] Василий Свиридов(53/175)[досье]
Если вы пишете свой запрос с JOIN'ом, то получается вы точно знаете какие поля вы хотите получить. Значит либо кидайте исключение, что данное поле не было инициализировано (если кто-то обращается к нему в коде), либо делайте ленивую загрузку.
спустя 4 часа 27 минут [обр] Александр Галкин(112/211)[досье]

Сложные запросы просто разбиваются на два этапа:

  1. Делаем обычный SQL-запрос любой степени навороченности, выбирающий список ID;
  2. По списку ID создаём объекты.

Бороться с SQL не нужно.

Я себе сделал в DBAL «расширение» над SQL: запрос SELECT * as user FROM users конвертируется в два запроса и в результат возвращаются уже объекты ActiveRecord. Можно сразу получить и объекты, и какие-то данные по ним посчитанные, очень удобно для отчётов.

Насчёт жертвования производительностью: запросы по первичным ключам настолько быстры, что иной раз могут быстрее кеша работать ;)

спустя 1 день 1 час [обр] triumvurat[досье]
конвертируется в два запроса и в результат возвращаются уже объекты ActiveRecord

почему в два?

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

и как выглядит возвращаемое значение? Объект и массив параметров, не вошедших в модель?

спустя 2 года 7 месяцев [обр] odiszapc[досье]
Попробуй Doctrine ORM. Вот здесь я готовлю перевод документации по Doctrine, возможно поможет:
http://odiszapc.ru/doctrine/
Powered by POEM™ Engine Copyright © 2002-2005