PHPixie 3.0 ORM или новый взгяд на ActiveRecord

    image Уже закончен долгообещанная третья версия PHPixie ORM компонента. Он теперь полностью независим от фреймворка и может спокойно использоваться сам по себе. В связи с этим, пока продолжается работа над другими компонентами и пишется документация, разработчики составили небольшой туториал для того чтобы можно было уже начать работу с ОРМ. Ниже я предоставлю его перевод и добавлю несколько вещей от себя, но сначала давайте посмотрим чем полезным порадует нас эта версия:

    • Большинство ORM используют Model классы, которые используются как для выполнения запросов так и для репрезентации самых записей в базе данных. Например так делают Kohana и Laravel. PHPixie разделила модель на Repository, Entity и Query, каждая из которых имеет строго свое предназначение.
    • Кроме SQL баз данных (SQLite, PostgreSQL, MySQL) полностью поддерживается также Mongo. Вы сможете связать отношениями модели с SQL баз данных с моделями хранящимися в коллекциях Mongo.
    • Чтобы поддерживать связи между разными базами ( например между таблицами в разных базах в MySQL ) много ORM используют отдельные запросы вместо join-ов и субзапросов. PHPixie же использует субзапросы всегда где это возможно.
    • Поддержка встроенных сущностей в Mongo
    • На 97% покрыта юнит тестами ( планируется 100% до конца этой недели), и еще на 75% функционалными тестами.
    • Эффективное использование Query позволяет уменьшить число запросов к базе. Например вам надо связать все топики автора с какими-то тегами. В большинстве ОРМ вам придется сначала найти топики, потом теги и тогда связать их ( 3 запроса к БД ). PHPixie позволяет сделать это за один запрос. Кстати пример этого есть в туториале снизу


    Надеюсь я смог вас заинтересовать, теперь посмотрим на пример с ссылки вверху в переводе:


    <?php
    
    require_once('vendor/autoload.php');
    
    $config = new \PHPixie\Config();
    
    //Инициализация компонента базы данных
    $database = new \PHPixie\Database($config->dataStorage(array(
        'default' => array(
            'driver' => 'pdo',
            'connection' => 'sqlite::memory:'
        )
    )));
    
    //И самой ОРМ
    $orm = new \PHPixie\ORM($database, $config->dataStorage(array(
        'relationships' => array(
            array(
                //У феи может быть много цветов
                'type'  => 'oneToMany',
                'owner' => 'fairy',
                'items' => 'flower'
            )
        )
    )));
    
    
    //Создание табличек
    $connection = $database->get('default');
    
    $connection->execute('
        CREATE TABLE fairies (
          id INTEGER PRIMARY KEY,
          name VARCHAR(255)
        )
    ');
    
    $connection->execute('
        CREATE TABLE flowers (
          id INTEGER PRIMARY KEY,
          name VARCHAR(255),
          fairy_id INTEGER
        )
    ');
    
    //Моделей больше не существует
    //Их заменяют Repositories, Entities и Query
    
    /*
    Мы все ненавидим когда сущности также являются запросами:
    $fairy->name = 'Trixie';
    $fairy->save();
    $fairy->where('name', 'Stella')->find();
    */
    
    //Репозитории создаются автоматически для существующих табличек
    $fairyRepository = $orm->get('fairy');
    $flowerRepository = $orm->get('flower');
    
    //Это все была инициализация
    //А теперь приступим
    
    //Создадим несколько фей
    
    $trixie = $fairyRepository->create();
    $trixie->name = 'Trixie';
    $trixie->save();
    
    //Версия покороче
    $fairyRepository
        ->create(array('name' => 'Stella'))
            ->save();
    
    //А еще нам понадобятся цветки
    
    foreach(array('Red', 'Yellow', 'Green', 'Purple') as $name) {
        $flowerRepository
            ->create(array('name' => $name))
                ->save();
    }
    
    
    //А теперь создадим Query
    //аналог WHERE `id` > 1 AND ( `name` = 'Green' OR `name` = 'Red')
    $green = $flowerRepository->query()
                ->where('id', '>', 1)
                ->startAndWhereGroup()
                    ->where('name', 'Green')
                    ->or('name', 'Red')
                ->endGroup()
                ->findOne();
    
    //или лаконичнее
    $green = $flowerRepository->query()
                ->where('id', '>', 1)
                ->and(function($q){
                      $q
                          ->where('name', 'Green')
                          ->or('name', 'Red')
                 })
                ->findOne();
    
    //Связи
    //Каждая связь добавляет свойства в Сущности и Запросы
    //В нашем случае свойство 'flowers' предоставляет методы add(), remove() и removeAll()
    //Это намного удобнее чем методы addFlower(), removeFlower() и removeAllFlowers() на самом объекте
    $trixie->flowers->add($green);
    
    //При связывании объектов, значение кешируется сразу в обе стороны
    //чтобы избежать дополнительных запросов к базе
    //В даном случае это кажется на таким уж важным, так как добиться такого эффекта
    //для oneToMany достаточно просто. Но для manyToMany оно тоже будет работать
    assert($green->fairy() == $trixie);
    
    //Теперь попробуем связать все цветы кроме Green c феей Stella
    //И уложиться в один запрос
    
    //Зададим запрос который нашел бы Стеллу
    $stellaQuery = $fairyRepository->query()
                        ->where('name', 'Stella');
    
    //И запрос который найдет цветы
    $allExceptGreen = $flowerRepository->query()
                        ->whereNot('name', 'Green');
    
    //И теперь магия
    $stellaQuery->flowers->add($allExceptGreen);
    
    
    //Также Query позволяет изменять сущности без выборки
    //Например переименуем цветок Purple в Blue
    $flowerRepository->query()
        ->where('name', 'Purple')
        ->update(array(
            'name' => 'Blue'
        ));
    
    //Можно найти фею у которой есть определенный цветок:
    $trixie = $fairyRepository->query()
                        ->relatedTo('flowers', $green)
                        ->findOne();
    
    //А теперь найдем всех фей у которых есть хотя бы один цветок
    //И сразу подгрузим все их цветки
    $fairies = $fairyRepository->query()
                        ->relatedTo('flowers')
                        ->find(array('flowers')); //так задается прелоудинг связей
    
    //И выведем их как простые объекты
    //Удобно для json_encode()
    print_r($fairies->asArray(true));
    
    
    //Больше примеров потом =)
    


    Попробовать этот пример у себя достаточно просто:

    git clone https://github.com/phpixie/orm
    cd orm/examples
     
    #если у вас еще нет Композера
    curl -sS https://getcomposer.org/installer | php
     
    php composer.phar install
    php quickstart.php
    


    Эта ORM разрабатывалась больше года и нам очень интересны все ваши вопросы и замечания, очень ждем комментариев.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 54

      +6
      И в итоге мы имеем не то сына не то дочь и не active record и не data mapper… Хотя к data mapper чуть ближе. Plain-old PHP объекты и Doctrine все-равно лучше. И это грустно ибо альтернативы вменяемой доктрине нет.
        –2
        Кстати, если кто-то использует доктрину, и думает, что у него «под капотом» только data mapper в распоряжении, то он очень сильно ошибается: github.com/doctrine/common/blob/master/lib/Doctrine/Common/Persistence/ObjectManagerAware.php#L32
          0
          Блин, а я так надеялся увидеть что-то серьезное после ваших слов… Ну да, можно в доктрине инджектить ObjectManager в сущности явно. Клево. Никогда не использовал и не планирую. А для тех кто хотел бы там все же предупреждение написано.
            +1
            Да в доктрине есть очень много интересного функционала, представленного исключительно в виде интерфейсов :)
            +2
            Ну на самом деле это дальше ActiveRecord, просто разнесенный по классам. Кстати раз я уже тут добавлю еще вкусного, расскажу об наследовании.

            В других ActiveRecord ORM приходиться наследовать саму модель. Это не очень удобно так как тогда в вашем классе сидит уйма логики от базы данных и это все потом надо мокать при тестировании. В PHPixie же наследование идет не от самих классов ОРМ а от спецыальних врапперов который проксюют все матоды в реальную ORM, упрощенно эти врапперы выглядят вот так:

            class EntityWrapper implements EntityInterface {
                 protected $entity;
            
                 public function __construct($entity) {
                       $this->entity = $entity;
                 }
            
                 public function save() {
                      $this->entity->save();
                 }
                 //....etc
            }
            
            class FairyEntity extends EntityWrapper {
            
            }
            


            Таким образом вашы собственные классы не будут содержать никакой ОРМ логики а значит из будет очень легко тестировать. А также это означет что разработчики могут менять ОРМ внутри как хотят а ваш код все равно будет к этому устойчив
              0
              У нас аналогично устроено, только я разнес базовые объекты для ORM и ODM в разные компоненты. Функционал конечно похож (общий предок DataEntity присутствует), но и различий прилично.
                0
                У «вас» это где? =)
                  0
                  twitter.com/spiralphp с недели на неделю буду выкладывать в публичный доступ.
                    0
                    Зафалловил )
                  0
                  Тьфу, перепутал про EntityWrapper, мне приходится наследоваться от модели т.к. в ней находятся необходимые мне валидации и фильтрации, а от базы там ровно три метода — delete, save и static find (возвращает Query как у вас). prntscr.com/6d8af9
              0
              Была одна клевая фича в вечноживом Пропеле. Очень клевая фича, которой не хватает многим ОРМам — кодогенерация запросов на основе колонок из БД. Например, запросы можно вызывать так:

              $stellaQuery = $fairyRepository->query()
                                  ->whereName('Stella');
              


              методы whereXXXX генерятся на основе БД схемы. И потом их очень удобно использовать — легко писать код и намного меньше вероятность ошибиться при написании запросов. Было б неплохо, если б пикси такое умел )
                0
                Я так делал давным давно, но в один прекрасный момент (кол-во колонок) это стало больше мешать чем помогать — prntscr.com/6d6zqw (проект 4 летней давности).
                  0
                  Тогда бы проишлось или делать кодогенерацию ( чего очень хотелось избежать ) или каждый раз выбирать список колонок ( дополнительний запрос ). Конечно можно сделать отдельную либу которая сгенерит враппер классы для конкретной таблички основываясь на табличке. Хорошая идея =)
                    0
                    Да, опциональная кодогенерация, подключаемая как трейт в класс репозитория — хороший вариант
                      +1
                      Зачем каждый раз? Для этого существует кэширование, например в YII 1 ORM была устроена именно так (не знаю насчет второй версии)
                        0
                        Во второй тоже.
                      0
                      Однако, здравствуйте. (doctrine 2)
                      $oRepo = $oApp->getManagerRegistry()->getRepository('ORM\Entity\User');
                      $oQuery = $oRepo->createQueryBuilder('u')->getQuery();
                      
                        0
                        Laravel так умеет делать) хотя я по привычке использую стандартный синтаксис.
                          +1
                          Вот скажите, чем это лучше такого варианта?

                          $repository
                              ->createQueryBuilder('f')
                              ->where('f.name', 'Stella')
                          // ...
                          ;
                          


                          Читается лучше, в случае если есть джойны так же проще разобраться… Камелькейс не самая удобная штука для «пробежаться глазами и найти».
                            0
                            Ну какие-то преимущества есть, например для абстракции можно сделать интерфейс с методом whereName() и тогда даже если ОРМ не будет можно самому как-то этот метод имплементировать
                              +1
                              Для репозитория — согласен, это удобно. Но для QueryBuilder — нет.
                          0
                          Что будет с любым ActiveRecord в случае если мы объект (к примеру User'а) ложим в Сессию. По логике связь с базой и вообще ORM прерывается, и после восстановления из базы у нас просто репрезентация данных, но(сразу дам пару костылей которые используют):
                          — К примеру нужно сделать правки в объекте восстановленном из сессии и мы их делаем, пытаемся вызвать save… и ошибка, нет связи.
                          1. Часто в Entity обратно получают подключение либо враппер через статические методы, превращая соединение в Signleton и блокируя возможность соединения с несколькими базами либо если хранится идентификатор это все равно не очень так как все равно нельзя явно это все переопределить.
                          2. Иногда с этой задачей справляются сами AuthenticationService либо SessionContainer, которые обратно при восстановлении инжектят нужные вещи внутрь той же Entity и мы получаем обратно работающий save. Но это очень костыльно, и не всегда можно точно определить откуда объект (к примеру в проекте у меня динамически увеличивается количество баз данных MongoDB под каждый «проект»).
                          — ActiveRecord обычно не хранят уже созданные Entity в каком-то UnitOfWork либо EntityPersister'е, поэтому очень часто при получении одного и той же записи мы получаем разные объекты, которые между собой не связаны, из-за этого возникают проблемы такие как: Мы обновляем текущего пользователя (полученного из запроса), но в сессии Юзер не обновился после привязки к менеджеру. Это решено достаточно хорошо в Data Mapper'ах где вместо оригинальной Entity часто отдает проксю, котора тянет данные не из реальной репрезентации а их общего хранилища оригиналов (__initializer__ в Doctrine который обращается к EntityPersister'у и берет оттуда данные). Что в данном случае у ActiveRecord:
                          1. Есть пару попыток реализации кеша этих самых Entity, но часто они сделаны не совсем гибко: отдается один и тот же объект, но есть проблема с случайным обновлением, которое еще не заперсистили, но мы уже везде его видим.

                          Времени мало, но таких «особенностей» можно описать очень много, я сам являюсь контрибьютором в Doctrine, и пишу собственную легковесную ORM совместимую с Doctrine но лишенную многих вещей влияющих на скорость.
                            +4
                            Ложыть в сессию всего юзера это уже антипаттерн сам по себе:

                            • Дамп всего объекта займет много места в сериализированном виде. Если таких сессий штук 10000 очень даже ощутимо
                            • Оверхед десериаоизвции такой сессии также заметен. Особенно если из этого объекта наи ничего не нужно ( например просто проверям залогинен ли юзер
                            • Имхо лучше просто хранить айди в сессии и все
                              +2
                              + проблема инвалидации данных юзера (сессий же может быть несколько)
                            +1
                            >> phpixie.com/

                            Сделали свой фреймворк, а сайт на вордпрессе. Как-то несерьезно.
                              +3
                              Так зачем еще и блог свой писать и форум ?) Время то не резиновое
                                +1
                                Ну это своего рода показывает уровень доверия к фреймворку. В целом я не вижу в этом ничего плохого… разве что для этого блога и сайта в целом можно было взять какой-нибудь генератор статических сайтов вроде sculpin или phrozn.
                                  0
                                  >> Так зачем еще и блог свой писать и форум ?) Время то не резиновое

                                  Хорошо, блог и форум сделали на вордпрессе. Ну на остальные 2,5 html-странички (homepage, документация) можно было и найти время.
                                    +1
                                    Так а зачем мне фреймворк для HTML странички? =))
                                    А документация сгенерена apigen-ом

                                    Ну я вам обещаю что что-что а HTML фреймворк точно вывести осилит =)
                                      0
                                      >> Так а зачем мне фреймворк для HTML странички? =))

                                      У вас не написано, для какого рода проектов создан фреймворк, поэтому считаем, что для создания простеньких HTML страничек в том числе он и создан. А теперь вопрос: зачем мне его использовать для сайтов, если вы его сами не используете?)
                              –3
                              функцыоналными — функциональными
                              Эффектывное — Эффективное
                                –4
                                хабр — такой хабр ) за что минус?)
                                  +2
                                  Потому что хабр такой хабр. Подобные вещи стоит сообщать в личку. Как минимум потому что после исправление подобные комменатрии выглядят глупо и не информативно.
                                    0
                                    Спасибо, учту.
                                  0
                                  Спасибо, исправил =)
                                  0
                                  бенчмарки не на Hello_World реквестую
                                    –1
                                    Бенчмарки будут всего фрейсворка когда буду закончены оставшеися компоненты. Можно посмотреть бенчмарки предидущей версии на www.techempower.com/benchmarks/
                                    0
                                    По примерам — это DBAL, а не ORM.
                                      0
                                      Почему-же? В DBAL нет связей между сущностями, это и есть отличительная черта ОРМ ( Object-relational mapping )
                                        +1
                                        Пардон, у меня сегодня глаза на заднице.
                                        0
                                        Похоже на ReadBean (http://redbeanphp.com/), только без query builder. Там тоже есть связи, можно не декларировать модели, а можно и декларировать.
                                          0
                                          Что вам помешало допилить автоматическое создание таблиц в БД и синхронизацию структуры?
                                            +1
                                            Идеология =) Такие действия будет выполнять компнент миграции
                                              0
                                              Компонент миграции только в планах, или уже можно использовать?
                                            0
                                            Очень интересная Orm, а вот если предположить, что у нас не цветок, как уникальная единица, а тип цветка с множеством свойств, тогда на уровне БД нам понадобится связь многие-ко-многим, по сути нужна таблица связки, и вопрос в том, сколько потребуется orm запросов, чтобы вывести все цветочки у какой либо феи, ну и наоборот у цветочка, посмотреть, каким феям он интересен, тоже один?
                                              0
                                              многие-ко-многим тоже получаются в один запрос =)
                                                0
                                                Скажите, что ещё и индексы сами проставляются, и я побегу использовать его в каком нибудь проекте:)
                                                  0
                                                  Таблицы автоматически не создаются, для этого потом будет модулт миграции
                                                    0
                                                    Вы наверное внешние ключи имеете ввиду?
                                                      0
                                                      Да, я про это
                                                0
                                                Несколько месяцев назад читал про ваш фремворк. И ORMка очень понравилась.
                                                Однако, тогда подумал, что разработка мертва. Рад, что ошибся, надо будет попробовать с чем-нибудь «для души».
                                                  0
                                                  Кстати да, сейчас присматриваюсь к выбору фраемворка и начал акцентрировать свое внимание на Pixie. Честно говоря что-то меня уже спугнуло. Но поделиться основным пугающем фактором все-таки решусь. Основная ветка github.com/dracony/PHPixie обновлялась год назад. Что создает иллюзию заброшенности. Хотя если зайти в профиль dracony, то будет видно. Что по сегодняшний день появляются новые коммиты относительно Pixie, но уже для модулей.
                                                    0
                                                    github.com/dracony/PHPixie это просто дефолтный проект, в котром подтягиваются все зависимости и запускается само приложение, там собственно нечего обновлять. Все разбито по компонентам.

                                                    Компоненты 3-й версии находятся тут: github.com/phpixie/ но она еще не закончена полностью

                                                Only users with full accounts can post comments. Log in, please.