Прошла первые боевые испытания и теперь представляется на милость хабрасообщества.
DaBase с одной стороны является
ORM т.к. предоставляет объектно-ориентированный доступ к базе данных, с другой стороны не совсем ORM т.к. не утруждает себя инициализацией и спецификацией структуры базы данных и взаимосвязей таблиц.
Особенности:
- Лёгкая: ~20kb вменяемого ООП кода.
- Быстрая и нетребовательная к системным ресурсам.
- Не требует никакой конфигурации, вы можете моментально начать использовать её на уже существующей схеме базы.
- Очень лаконичная, позволяет в одну читабельную строку кода выполнить несколько различных операций.
- Имеет полноценные средства для генерирования(экранирования) SQL запросов по аналогии с DbSimple.
- Поддерживает псевдо-JOIN таблиц на базе соответствия имён таблиц и полей-индексов
- Поддерживает создание специфицированных моделей данных с конфигурацией правил самовалидации полей
В ближайших планах:
- Поддержка других БД (помимо MySQL) на базе PDO драйвера
- Модуль автоматического кеширования
- Инициализация структуры таблиц из лаконичного XML c поддержкой FOREIGN KEYS
- Умные JOIN-ы с авторазбором на вложенные объекты
- Вложенные логические условия для фильтров и группировки
SVN:
http://dabase.googlecode.com/svn/trunk
Архив релиза:
http://dabase.googlecode.com/files/DaBase_1.0.zip
Руководство с примерами:
http://code.google.com/p/dabase/wiki/UserGuide
Страница проекта на Google Code:
http://code.google.com/p/dabase
Всем желающим присоединиться к проекту — очень рад :)
Всем спасибо за конструктивную критику и отзывы!
UPD: Народ, я просил чтобы критика была конструктивная, а тут большинство претензий исходит либо из-за того что руководство не полностью прочитали, либа из-за банального недопонимания. Я понимаю, что руководство на английском и тема холиварная, но давайте будем внимательнее и терпимее.
UPD: При всей лаконичности DaBase, уважаемый хабраюзер, если ты работаешь над крупным проектом с базой данных включающей множественные связи таблиц и есть необходимость полноценной спецификации их отношений, то лучше обратить внимание на такие системы как
Propel и
Doctrine.
комментарии (102)
гхм… почему $db->users не знает откуда нужно выбирать данные?
К тому же $db->users не обязательно указывает на таблицу `users` (в том случае если определён класс User с константой table = 'some_users_tablename')
Тут таблицы могут вообще не описываться, если не надо. А если и надо, то они описываются посредством классов конечных объектов этих таблиц.
Я тогда не понимаю, зачем нужен $db->users? Чем это круче чем $db->getByQuery('… from User ...', 'User_Class', $isActive) [то есть некий map-query-to-object]?
DaBase_Db — исключительно гибкая работа с запросами с маппингом через $db->someTable на DaBase_Getter
DaBase_Getter разруливает всё что касается обработки результатов запросов в контексте соответствующих объектов классов (если они объявлены) или DaBase_Object (если не объявлены)
О чём речь вообще, зачем в $db->users->getByQuery имя таблицы прописывалось? Затем чтобы кастомные запросы формировать. Если кастомные не надо, то и указывать ничего не надо.
foreach ($result as $k => $v) {
$obj = new $param;
$this->fill_fields($obj, $v);
}
Давайте по делу, конструктивно. Если что непонятно, то сперва спрашивайте.
В DaBase не совсем заурядная архитектура и это специально было сделано, с практичной точки зрения.
Это исключительно имхо.
Конструкция
db->users->getByQuery('SELECT * FROM # WHERE `isActive`=?', $db->users->getTable(), $isActive);
выглядит, как минимум странно. Если Вы можете узнать имя таблицы тут, вызвав $db->users->getTable(), то, почему Вы не можете сделать тоже самое внутри getByQuery(), метода того же объекта $db->users — совершенно не ясно.
Логичнее выглядело бы, наверное, так:
$db->users->getByCriteria( new DaBase_Criteria(array('isActive' => $isActive)) );
Также, не ясно, зачем реализован доступ к моделям через $db->? Вы пишете:
> К тому же $db->users не обязательно указывает на таблицу `users` (в том случае если определён класс User с константой table = 'some_users_tablename')
Т.о., собственно, mapping осуществляется классами? Почему не сделать доступ к моделям через объекты этих классов?
class User extends DaBase_Model
{
const DABASE_TABLE = «some_users_tablename»;
}
$user = new User();
$user = User::getByCriteria();
и т.д.
В любом случае, необходимость писать SQL для такой простой выборки, как приведена выше, как-то сразу ставит под сомнение весь продукт.
> (в том случае если определён класс User с константой table = 'some_users_tablename')
очень удивило.
я ожидал:
или фатала (переопределение константы) или отсутствия константы в наследнике (угу, из-за отсутствия LSB), но на гора имеем вполне рабочий код:
class a
{
const ORLY = 'lol';
}
class b extends a
{
//const ORLY = 'lol2';
}
echo b::ORLY;
и с комментом и без оно работает… чудеса :-)
class A
{
const CNST = «this is A»;
public function getConst()
{
echo self::CNST;
}
}
class B extends A
{
const CNST = «this is B»;
}
$obj = new B;
$obj->getConst(); // выведет «this is A»;
Соответственно, в 5.3 можно self:: заменить на static:: и тогда будет выведено «this is B»;
До 5.3 такое тоже реализовывалось:
$constName = get_class($this). "::CNST";
echo constant($constName);
Но это анальный доступ к данным.
я о том, что меня удивило, что b::ORLY будет существовать.
конкретно против пхп ничего не имею — он очень клёвый и программирование на нём занимает процентов 95 моего программирования. но уж больно в нём много неочевидных вещей :-)
С этим, к большому сожалению, полностью согласен :)
Вы не поняли, $db->users->getByQuery() нужен для формирования кастомных запросов по конкретным таблицам, а куда там название таблицы автоматом подставлять когда эти кастомные запросы на то и кастомные чтобы вы сами прописывали что куда надо.
> $db->users->getByCriteria( new DaBase_Criteria(array('isActive' => $isActive)) );
А вот от таких схем я умышленно ушёл с самого начала т.к. ДЛЯ МЕНЯ это слишком избыточный код, избыточная нагрузка на систему… ну и вообще с таким подходом уж лучше на голом SQL писать.
> В любом случае, необходимость писать SQL для такой простой выборки, как приведена выше, как-то сразу ставит под сомнение весь продукт.
Пример с $db->users->getByQuery() был приведён для примера. Понятно что в DaBase тот же самый запрос можно написать куда короче: $db->users->limit(5)->get().
Согласен… но для простых критериев можно писать, напрмер:
$db->users->getByCriteria(array('isActive' => $isActive));
что может в каком-то смысле и избыточно, но не избыточнее кастомного запроса однозначно (собственно какова цель продукта — не писать SQL и работать с объектами, т.е. ORM, или что-то другое?).
> $db->users->limit(5)->get();
Такая выборка не учитывает isActive. Получается для любой выборки с критерием/фильтром приходится писать кастомный запрос?
Критерии есть, но они только логического типа AND, по типу фильтров:
$db->users->isActive(true)->isModerator(true)->get(); // вот и все критерии
Можно ещё так:
$db->users->posts(50, '>')->get();
Это кстати примеры из руководства :)
зачем нужно указывать таблицу явно и даже запрос (это вообще работа сугубо ОРМа, как получить, как обработать запрос итд).
нужны другой тип пользователей — отнаследуйте класс, определите в нём другую таблицу
class Yet_Another_Users extends Users
{
private $table = 'ya_users';
как-то так.
согласно ему же — один класс отражается ровно на одну таблицу. и наоборот (бывают исключения, но это не тот случай)
:)
Я к тому, что многие проблемы широкораспространённых ОРМ-ов как раз в ограничении отображения объект<->таблица.
в силу вышесказанного: очень слабо представляю метаописание объекта, который будет отражаться на набор таблиц.
И на то он и DaBase, чтобы не надо было для каждой таблицы отдельный класс создавать т.к. это не всегда критично.
И насчёт:
class Yet_Another_Users extends Users
В DaBase это всё есть, см. руководство.
немного уточню: в моём понимании 1 класс конкретно отражает одну таблицу бд. обратное верно
существуют исключения, но с ними всё не так просто и это всё не имеет отношения к текущему разговору.
Пробежался по документации этого RedBean http://www.redbeanphp.com/#/beans. Не знаю чем он лучше всех прочих ORM, хотя кстати всем прочим я предпочитаю DaBase (хоть она и не совсем и ORM). Не аргумент?
Тогда вот, пробежался по исходникам… помимо претензий к архитектуре, вот конкретный пример:
public function selectRecord($type, $ids) {
$type=$this->adapter->escape($type);
$rows = $this->adapter->get( "SELECT * FROM `$type` WHERE id IN ( ".implode(",",$ids)." )");
if ($rows && is_array($rows) && count($rows)>0) {
return $rows;
}
else {
return null;
}
}
А где элементарное экранирование? Не стал бы я таким разработчикам свою базу доверять.
Для RedBean так же как и у вас, не нужно писать конфигов или схему БД. Достаточно списка классов, которым на ходу можно присваивать любые поля.
require('oodb.php');
RedBean_Setup::kickstartDev('', 'mysql:host=localhost;dbname=mydatabase' );
//В режиме разработки по ходу того что мы будем делать далее создадутся соответствующие таблицы в БД, так же их структура может меняться налету.
Есть и режим, в котором изменение структуры БД запрещено — уже для продакшена.
Redbean_OODB::Gen('Post,Comment');//теперь у нас есть классы Page,Post,Comment
$p = new Post;
$p->caption='bla-bla-bla';
$p->setRating(1500);//можно и методами
$p->text=$some_text;
$c = new Comment;
$c->text='qweqweqwe';
$p->add($c);
$id = $p->save();
Доступ к сохранённому экземпляру:
$p2 = new Post( $id );
echo $p2->caption;
$p2->author='Valera';
$p2->save(); — вот теперь в структуру таблицы будет добавлено новое поле!
Можно импортировать поля из массива:
$employee->import( array('name'=>'John','badge'=>123) );
Или экспортировать в массив:
$arr = $bean->exportAsArr();
Есть поиск по SQL:
$persons = Person::where(' name LIKE {nm} and age > {ag} order by customerid asc ',
array('name'=>'Geor%','age'=>50))->getBeans();
Просто получить все записи из таблицы (массив массивов) можно через:
$persons = Person::findAll();
Можно также хранить древовидные структуры, используется Ad.List
И многое другое
Стоп. Опять кто-то не внимательно руководство читал. Для DaBase не нужен никакой список классов!
Вы конечно извинете, но я уже устал тут разглагольствовать. Чисто субъективно этот Redbeans местами чепушной какой-то. И не надо говорить, что он удобней чем DaBase т.к. банально судя по примерам которые вы привели это не так.
Короче, если вам так нравится этот ORM, то напишите о нём полноценную статью на хабре, лично мне будет очень интересно почитать. А критика, будьте уверены, народ вам ещё много чего «хорошего» про него расскажет.
Да и вообще… это сложно назвать ORM'ом.
DaBase — Lightweight pseudo-ORM for PHP
Потому, что если это был в чистом виде ORM, то его исходник весил бы соответственно в 50-100 раз тяжелее, и синтаксис был бы также обременён всякими излишествами.
Принципиальная особенность DaBase в его лаконичности.
И насчёт того, что там почти везде SQL — далеко не везде. Работа с чистым SQL там приводится в основном для примера работы со своими кастомными запросами, от которых ни в одной ORM вам не уйти (и слава богу), и всё чем вам может помочь ORM в этом вопросе это предоставить удобный инструмент для генерации этих самых кастомных запросов.
Вот как один из аспектов DaBase (DaBase_Db в частности) это и есть — удобный интерфейс для генерации кастомных запросов.
Но помимо этого есть и DaBase_Getter и DaBase_Db которые как раз то и делают, что пытаются максимально абстрагировать вас от написания регулярных запросов.
Надеюсь, что понятно объяснил.
И ещё, какая нафик разница сколько весит серверсайд приложение? Главное это сколько он памяти жрет, а не места.
2. Если не ошибаюсь он работает с базой через свой драйвер, а в PHP 5.3 используется нативная библиотека MySQL, которой он уступает в скорости.
2. Нет: forge.mysql.com/wiki/PHP_PDO_MYSQLND
Мне изначально хочется понять, кроме негативной критики будут ли позитивные причины причины заниматься её развитием.
2. Сразу скажу, использовать её я не буду :-)
1. Явно ненужное Dependency Injection ввиде DataBase_Object
2. Создание DataBase_Getter'а каждый раз, когда идёт обращение к DataBase_Db::getGetter(), что конечно же очень «оптимально» для памяти.
3. Отсутствие адаптера для подключения.
4. Отсутствие программной поддержки блокировок ( см. «Пессимистическая блокировка», «Оптимистическая блокировка»), т.е. оно есть но я никак не смогу узнать заблокирована ли таблица в настоящий момент, и т.д.
2. Для памяти это ерунда т.к. сам по себе DaBase_Getter почти ничего не весит, к тому же по одному крохотному объекту на выборку это тем более ни о чём по сравнению с 10-20 объектами на выборку у тех же Propel/Doctrine/etc.
Вообще странная претензия т.к. сам результат из 100 объектов выборки будет по сути в 100 раз тяжелее объекта запроса выборки, но и всё равно при этом очень мало памяти задействуется.
3. Адаптер для подключения будет, и вполне возможно что на базе PDO
4. Про блокировки пока не думал даже, но буду учитывать. Пока обходимся транзакциями и блокировками на уровне mysql.
2. Зачем это допускать, если этого можно избежать? См. Фабрика объектов.
P.S Ещё подумайте в сторону наследования.
2. Я знаю, что такое фабрика объектов, но так или иначе невозможно избежать создания отдельного объекта DaBase_Getter на каждый отдельный запрос. Надо понимать, что это объект, который хранит конфигурацию запроса: фильтры, сортировки, join-ы.
Что же теперь, сделать один глобальный Getter и в нём стеком хранить все текущие конфигурации запросов? Там по накладкам памяти ещё больше уйдёт.
P.S. В сторону наследования чего от чего? Там всего 3 базовых класса. Может имеется ввиду создание абстрактных прородителей?
$db->user->…
$db->user_gold->…
Мне, допустим, нужно чтобы user и user_gold — были одной и той же таблицой.
На DaBase это просто делается
наследуем User от DaBase_Object и прописываем const table = 'users'
наследуем GoldUser от User и… и всё
$db->users и $db->usersGold будут выдавать объекты одной таблицы, но разных классов
Можно ли сделать так, чтобы $db->usersGold выдавал только объекты с isGold = 1?
public $isGold = 1;
и для верности можно ещё валидацию на него повесить, типа
$validator->add(new Validator_Rule_Regexp('/^1$/'))
оно по умолчанию будет автоматом при всех insert-ах и update-ах срабатывать
$validator->add('isGold', new Validator_Rule_Regexp('/^1$/'))
$db->usersGold->limit(10)->get() == select * from users where isGold = 1 limit 10
или нет?
Но просто в этом случае теряется некая прозрачность логики работы объектов. Я вовсе не считаю правильным возлагать на инструмент абстрагирования с базой функции контроля бизнес процессов, потому как в этом случае всем прочим разработчикам придётся каждый раз обращаясь к методам библиотеки учитывать что они могли бы быть 15 раз переопределны.
В вашем случае мне кажется проще всего было бы ограничиться одной моделью User, при этом добавив в своё API метод возвращаемый Getter с фильтром по isGold. Т.е.:
public function getGoldUsersGetter() {
return $this->db->users->isGold(true);
}
B потом уже делайте с ним что хотите.
создаёте класс
class GoldUsers extends DaBase_Getter {
protected $tableName = 'users';
protected $objectsClass = 'User';
protected function postInit() {
$this->isGold(true);
}
}
И всё, теперь обращаясь: $db->goldUsers->get() всегда получаете геттер класса GoldUsers c предустановленным фильтром.
Database_Object вполне себе может включать в себя экземпляр связуемого класса, и связывать его поля с полями таблицы через Reflection.
> Database_Object вполне себе может включать в себя экземпляр связуемого класса, и связывать его поля с полями таблицы через Reflection.
Не совсем понял как это, но до Reflection точно не хочется опускаться т.к. это на мой взгляд уже изврат.
А к чему тогда эти ваши слова:
> ORM по определению должна абстрагировать бизнес-логику от SQL
API должно абстрагировать бизнес логику от ORM, а ORM в свою очередь в идеале должно абстрагировать способ хранения и доступа к данным.
Database_Object вполне себе может включать в себя экземпляр связуемого класса, и связывать его поля с полями таблицы через Reflection.
> Database_Object вполне себе может включать в себя экземпляр связуемого класса, и связывать его поля с полями таблицы через Reflection.
Не совсем понял как это, но до Reflection точно не хочется опускаться т.к. это на мой взгляд уже изврат.
Вы лучше посмотрите как это реализовано в концептуально плане в Hibernate и в iBatis (в ней как раз всё очень хорошо с кастомными запросами и user defined связывателями полей таблицы с классом ). Пусть они и на Java, но это не важно для понимания основополагающих принципов.
Грубо так сравнивать, Hibernate больше подходит для Enterprise-решений, и при том если с нуля только на нём и разрабатывать. А попробуйте действующий проект на Hibernate зарефакторить, непросто это очень.
На на самом деле зарефакторить на поддержку кастомных PK-полей дело плёвое, просто в константу вынести название PK-поля. Дальше начинаются вариации с составными PK-полями, но их уже как-то совсем избыточно учитывать.
$comment->getUser()->getName() — Так можно?
И еще. ОРМ никак не запоминает объекты, поэтому в определенный момент времени у меня может быть 2 объекта в памяти, которые будут ссылаться на одну и туже запись в таблице, а это очень плохо.
Насчёт двух объектов одной и той же строки: момент с одной стороны важный, но с другой стороны не совсем т.к. у вас ведь если подумать в один момент времени один и тот же объект может быть задействован в разных скриптах… а это уж точно почти никак не проконтролировать.
Но меня в микро-тасках есть этот вопрос, хочу унаследовать решение у LightOrm for PHP.
C DaBase все ясно, но почему Николай Воронов?
Я не верю, что после просмотра кода Propel'a или Doctrine можно написать такое. Неужели вы не смотрели другие ORM перед тем как писать свою?
Знаю я про Propel, а Doctrine так лучше бы и не знал вовсе. Ещё раз: DaBase — это не ORM в буквальном смысле. Вы документацию вообще видели, на страницу проекта хотя бы заходили, читали что там написано?
И про PDO я тоже знаю, но бонусы его пока не актуальны были. Единственное в чём он действительно может быть полезен так это как адаптер к другим БД, как-нибудь прикручу на досуге.
А вы не понимаете, что для ряда проектов Doctrine и Propel просто неприлично тяжелы и большинство просто не возьмётся их осваивать? Хотя я вот освоил, но всё равно порой не актуально мне на тракторе ковёр пылесосить. Вот для таких целей и использую DaBase — просто зная структуру базы работаю с ней как с объектной моделью, а где критично добавляю спецификацию моделей с набором правил валидации.
PDO, в отличие от mysql_ позволяет нормально вставлять константные значения в запросы. А вы ваше экранирование где-нибудь обязательно забудете и получите дыру в безопасности. Да и кроме этого плюсы есть, которые очевидны если почитать документацию.
Я не понимаю почему надо использовать велосипед сомнительного качества с крайне низким кол-вом функциональности ради «легкости». Освоить тот же Propel туда как проще чем вашу ORM. Документация в разы лучше и сам код логичнее. У меня на это ушло ну максимум час.
Вы предложили что-то революционное? Что-то удобнее существующего? Удачно совместили плюсы других ORM и не повторили минусов? Ответы на все вопросы: нет. Единственный «аргумент» — легкость, который отнюдь не является плюсом и являться им не может. Ибо не кол-вом кода мерится качество и удобство продукта.
Ну один самый очевидный способ и так все знают — чистый SQL, всё что далее — оптимизация способов его генерации, и тут уж не так много методов, чтобы их запомнить. Штук 5 всего, а ключевых так вообще 2-3.
> PDO, в отличие от mysql_ позволяет нормально вставлять константные значения в запросы. А вы ваше экранирование где-нибудь обязательно забудете и получите дыру в безопасности.
Ну и DaBase нормально с константами работает, в руководстве ведь написано. И какая разница, что вы забудете в PDO значение переменной без prepare вставить, или в DaBase так же? Если человек дурак…
> То, что добавить поддержку других баз невозможно принципиально — дикий фейл. Вы вообще про полиморфизм слышали что-нибудь?
Слышал, только пока необходимости не было, не думал, что для большинства этот вопрос так остро стоит. На днях прикручу PDO с его драйверами под прочие БД и зарефакторю генерацию соответствующего SQL.
> Освоить тот же Propel туда как проще чем вашу ORM. Документация в разы лучше и сам код логичнее. У меня на это ушло ну максимум час.
Спорный вопрос. Properl & Doctrine & etc это полноценные и тяжелые ORM, они требуют полной спецификации базы и соответствующей конкретики в обращении с ней. Вы высоко нагруженные проекты на Propel когда-нибудь профайлили? Случается, что в конечном счёте приходится или Propel откручивать, или вторую серверную стойку на Xeon-ах прикручивать.
> Вы предложили что-то революционное? Удачно совместили плюсы других ORM и не повторили минусов?
Я предложил альтернативу, и ни в коем случае не конкуренцию тяжеловесным ORM пакетам. Главная задача серьёзных ORM пакетов — универсальность, за что приходится платить производительностью и удобством использования. Я преследовал главный принцип практичности, т.к. именно этого мне часто и не хватает в других библиотеках. А вдруг не я один такой?
> Что-то удобнее существующего?
Удобнее? Я считаю да, во многих моментах удобнее. Надо будет в топике подправить, что сие не есть Enterprise-решение.
> Удачно совместили плюсы других ORM и не повторили минусов?
А это вообще возможно? Простите, но как вы себе представляете пылесос и фен в одном флаконе?
> Единственный «аргумент» — легкость, который отнюдь не является плюсом и являться им не может.
Скажите это тем, кто использует Twitter вместо Livejournal. Вы слишком критичны. Я не спорю, что есть широкий ряд задач, для которых актуальнее использовать Properl или Doctrine, но всё-таки считаю, что спектр адекватного применения DaBase также достаточно широк.
Вы хотите сказать, что Propel был узким местом вашего проекта? В чем именно? Кроме кривой системы кеширования, которую можно всегда заменить, я не могу представить что там может тормозить. Быть может, стоило, наконец, освоить какой-то другой язык кроме PHP, если ORM библиотека является узки местом?
Расскажите как мне работать с колонкой order, если она есть в таблице? Как мне сделать условие «IS NOT NULL»?
Видел пару раз удачные экземпляры, но, к сожалению, ссылок найти сейчас не смог.
Я бы не стал сравнивать клиентскую часть и серверный код.
В том, что при создании запроса генерится слишком много левых (Propel-овских объектов), в том, что сам код перевешивает код всей системы и т.д.
> В том же самом пропеле я в одном XML конфиге получаю структуру базы. Легко, доступно и в одном месте.
А мне вот не нравится их синтексис XML-конфига, я в SQLYog быстрее базу заделаю. А вообще это всё клёво если с нуля систему проектировать, а если готовая — пилим гири?
> Расскажите как мне работать с колонкой order, если она есть в таблице? Как мне сделать условие «IS NOT NULL»?
$db->users->order('order')->get(); // :)
> Я бы не стал сравнивать клиентскую часть и серверный код.
Тут вопрос не в клиентской части и серверной, а в том какими граблями волосы расчёсывать :)
YAML не пробовали?
Я правильно понимаю, что проект был настолько оптимизирован (и база тоже), что ORM стала реально узким местом?
Я криво написал, а вы не поняли. Как сделать фильтр по этой колонке. Сейчас фильтрация у вас идет методами с таким же названием как и колонка. Метод order же уже существует, а, значит, по значению эту колонку будет не отфильтровать.
Нет, хотя наверное стоило.
> Можно конкретики? Памяти слишком много отжиралось, слишком долго выполнялось? Что именно?
Памяти и процессорного времени. В проекте, в котором очень высокая нагрузка на фронтенд, более 500kb Propel-овского кода начинают давать о себе знать. Даже использование opcode-ного кеширование не спасало.
> Как сделать фильтр по этой колонке.
$db->users->filter('order', 5, '>')->get();
С моей точки зрения главное — ORM должна обеспечить разработчика таким функционалом, чтобы ему не пришлось написать ни строчки SQL (оставив такую возможность).
На самом деле «DaBase» до ORM допилить не так сложно, у меня был подобный прототип — code.google.com/p/zenmysql/ Вторая версия, которую вылизываю последние пару недель теперь полностью поддерживает CRUD и умеет возвращать нужные типы объектов. В сочетании с генератором классов — теперь объект сам знает свои PK, список полей и дефолтных значений, связи с другими таблицами, поля можно оборачивать в другие классы с настройкой свойств. Но это не набор функционала любой ORM.
Наворачивать можно до бесконечности, но такие обертки для баз, таблиц, рядов в основе, как DaBase — очень помогают при написании собственно, самой ORM. Также, в моем случае, основной задачей первой версии zenmysql было получение ссылок (mysql link resource) на 3-4 базы данных одновременно, и абстракция mysql_query($sql, $dblink) при каждом запросе в виде $table->q($sql)