За последние 24 часа нас посетил 24871 программист и 1687 роботов. Сейчас ищут 904 программиста ...

Мои Database Abstraction и Data Mapper

Тема в разделе "PHP и базы данных", создана пользователем artoodetoo, 21 апр 2014.

  1. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    Выношу на суд классы работы с БД. Причем разного уровня абстракции.

    1. DBAL: Оболочка над mysqli/PDO для удобной работы с параметрами. Ничего фантастического, просто чуть более удобный API, настраиваемый префикс для имен таблиц и параметр-массив, — то чего мне не хватает в нативных драйверах БД.
    Код (PHP):
    1. $rows = $db->query("SELECT * FROM `:p_users` WHERE `id` IN(:ids)", ['ids' => $ids])
    2.                 ->fetchAssocAll();
    2. ORM: Очень простая реализация шаблона Data Mapper. Делал под влиянием Doctrine, но конкурировать не планирую. Хотелось чего-то более легковесного, но при этом чтобы покрывало 90% задач.

    Паттерн Data Mapper, в отличие от Active Record, не заставляет вас наследовать все объекты от абстрактной записи. Идея такова: пусть класс предметной области заботится о предметной области, а отображеним его в базу пусть занимается отдельный класс. Для большинства простых случаев подойдет универсальный репозиторий, а для сложных можно отнаследовать новый класс-репозиторий и перекрыть пару методов.

    Пока всё даже не Beta, а очень предварительное. Так что ваши замечания могут реально повлиять на развитие фреймворка.
    Из коробки в DM есть поддержка автоинкремента и отображение имен $object->fieldName на `objects`.`field_name`.

    Код (PHP):
    1. namespace MySpace;
    2.  
    3. class User
    4. {
    5.   public $id = 0;
    6.   public $userName = '';
    7.   public $groupId = 1;
    8. }
    Код (PHP):
    1. CREATE TABLE `users` (
    2.   `id` INT NOT NULL AUTO_INC,
    3.   `user_name` varchar(200) NOT NULL DEFAULT '',
    4.   `group_id` INT NOT NULL DEFAULT 1,
    5.   PRIMARY KEY (`id`)
    6. ); 
    Код (PHP):
    1. // INSERT
    2. $user = new \MySpace\User();
    3. $user->userName = 'xxx';
    4. $entityManager->persist($user);
    5. echo "New id: {$user->id}\n";
    6. // UPDATE
    7. $user = $entityManager->find('MySpace\\User', 1);
    8. $user->userName = 'ololoich';
    9. $entityManager->persist($user);
    Код (PHP):
    1. echo 'Count: '.$entityManager->count('MySpace\\User', ['groupId' => 1]),"\n";
    2.  
    3. $userIterator = $entityManager->findBy('MySpace\\User', ['groupId' => 1]);
    4. foreach ($userIterator as $u) {
    5.   echo $->userName."\n";
    6. }
    https://github.com/scrap-bin/unicycle
    Есть зачатки документации и тесты. Повторять всё, что там написано не буду. Кому захочется запустить на пробу, начните с прочтения app/doc/install.ru.md, там требования и шаги по развертыванию расписаны.
    Само тестовое приложение просто выводит "Привет Мир!". А вот тесты могут рассказать многое.

    Спрашивайте — я поясню чо как. Буду благодарен за замечания и предложения.
     
  2. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    это не опечатка?
     
  3. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    нед. это фича.

    Добавлено спустя 35 секунд:
     
  4. igordata

    igordata Суперстар
    Команда форума Модератор

    С нами с:
    18 мар 2010
    Сообщения:
    32.408
    Симпатии:
    1.768
    а если там уже `userName`? зачем это изменение?
     
  5. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    Можно определить другую карту отображения имён через конфигурацию. Но нельзя переопределить одно поле, придется описывать всю таблицу.
    Я использую такие умолчания, к которым привык.
     
  6. runcore

    runcore Старожил

    С нами с:
    12 окт 2012
    Сообщения:
    3.625
    Симпатии:
    158
    $entityManager->persist($user);
    встретив такую строку, я незнаю ЧТО ИМЕННО щас происходит. приходится изучать тонну кода выше, чтоб понять, инсерт там или апдейт... совершенно неинтуитивно

    $user = $entityManager->find('MySpace\\User', 1);
    постоянно указывать НС класса - тудаже. многословно както.

    ушел от наследования классов от AR(в одном месте) - к чему пришел?:
    - жесткая зависимость осталась, только в прикладном коде теперь
    - зависимость описывается не в одном месте(компактно) - а 'размазана' по всему приложению. антипрофит
    - вместо работы с экземпляром класса, работаем с классами. антипаттерн какойто

    в общем плюсов подхода ненаблюдаю, минусы(ИМХО) очевидны. я щас не про производительность, а именно удобство написания, сопровождения кода. интуитивность, компактность
     
  7. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    На это можно ответить, что нам необязательно знать что происходит внутри: мы просто сохраняем объект. Может там вообще non-SQL, почему мы должны об этом думать на прикладном уровне?
     
  8. runcore

    runcore Старожил

    С нами с:
    12 окт 2012
    Сообщения:
    3.625
    Симпатии:
    158
    это ответ мне?
    тогда невижу связи. естественно на прикладном уровне мне нужна простая абстракция, с которой я работаю. сохраняю данные. выбираю данные.
    т.е. я её родил. и работаю с ней дальше. для реализации этого поведения достаточно классического подхода - инстанцирования объекта от класса который умеет и знает все что нужно. и назвать его я могу хоть $obj. и обращаться к нему. тупо - меньше кодить. и любая IDE подскажет мне все что он умеет. а тут. один god -манагер который умеет всё и для любых классов. но эту связь нужно постоянно прописывать в прикладном коде. раз за разом. неудобно.
     
  9. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    Вот именно — абстракция, а не буквальное повторение. Важно, что мы сохраняем состояние объекта, а не то, какой оператор SQL будет этому соответствовать и вообще есть ли там SQL. Типа того.

    Для вещей, о которых ты говоришь, когда весь SQL на виду, есть уровень DBAL. Там абстракций минимум.
    Код (Text):
    1. $id = $db->query("INSERT blablabla", [params])->insertId();
    Немного расскажу про иерархию классов. EntityManager это "точка входа", через которую мы вытягиваем остальные объекты. Реально отображением занимается EntityRepository. Теоретически можно для каждой сущности держать по отдельному классу-репозиторию, в котором будет вся специфика расписана.
    Можно сказать, что репозиторий — это Модель в иерархии MVC. А менеджер это сервис-локатор для поиска репозитория.
    Если специальный класс репозитория не описан, то используется репозиторий-по-умолчанию.

    Чтобы получить экземпляр репозитория, для сущности, надо вызвать метод EntityManager->getRepository($entity).
    Но для краткости EntityManager предоставляет "шорткаты", чтобы этот шаг можно было пропустить. Это так сказать, синтаксический сахарок. Полный вариант получения объекта без магии выглядел бы так:
    Код (PHP):
    1. $user = $entityManager->getRepository('MySpace\\User')->find(1); 
    2. // а можно сократить до $entityManager->find('MySpace\\User', 1)  
    Т.е. найти репозиторий для класса User, затем уже у него запросить объект, соответствующий значению первичного ключа == 1.
    Все методы find* ищите в классе EntityRepository !

    Мне тоже не нравится такой способ упоминания класса — как строка, но нет в PHP типа ClassRef, как в некоторых языках. Поэтому надо либо строку с именем класса передать или готовый объект нужного класса.

    Для наборов записей есть класс EntityIterator — его можно получить, например, по $repository->findBy([condition], [order], [limit]),
    Итератор можно подставить в foreach()

    Добавлено спустя 11 минут 44 секунды:
    ёпрст! столько опечаток совершаю, когда тороплюсь ответить, что потом очень долго надо вычитывать и исправлять )))) лучше не торопиться.

    Добавлено спустя 12 минут 30 секунд:
    я подумал над тем, что igordata сказал и, пожалуй, сделаю маппинг FooBar -> foo_bar опциональным. будет специальная настройка для этого.
     
  10. Fell-x27

    Fell-x27 Суперстар
    Команда форума Модератор

    С нами с:
    25 июл 2013
    Сообщения:
    12.156
    Симпатии:
    1.771
    Адрес:
    :сердА
    А с производительностью как? Есть сравнительные тесты с чем-нибудь аналогичным?
     
  11. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.128
    Симпатии:
    1.248
    Адрес:
    там-сям
    Тестов производительности нет и не будет в ближайшее время.
    DBAL это такая тонкая прослойка, что скорость очевидно не сильно страдает. Нефиг там мерять.
    ORM на таком раннем этапе меня больше интересует удобство использования и вообще ORM не ради скорости выполнения делается.

    ORM по интерфесу он напоминает доктрину, но это вещи разных весовых категорий, я сразу это оговорил. Доктрина с ее прокси классами и своим собственным языком DQL будет в разы медленнее, но я ей не судья ))) Опять не ради скорости она делается!

    Тема меня волнует, moar фидбека!