RedBeanPHP — CodeFirst PHP фреймворк

RedBeanPHP
В данном посте речь пойдет об весьма интересном ORM фреймворке RedBeanPHP. Примечателен он, прежде всего, возможностью создавать структуру базы данных на лету. К тому же фреймворк прост в использовании как две копейки. Моё повествование будет разделено на 3 части.
Во второй части основная тема будет — модели. В третей — изменение логики работы фреймворка.

Перед написанием поста я потрудился и сделал тестовое приложение с 15 000 записей, для того, что бы убедиться на своем опыте в возможности невероятно облегчить работу. Ведь я наверно не один, кто прописывает поля в нескольких местах с жутким осознанием бессмысленности этой работы, особенно на начальном этапе разработки. Наконец появился аналог Entity Framework Code First из .NET, который в свое время вызвал у меня дикий восторг. Итак по порядку.

Истоки


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

Начало


Для подключения необходимо добавить лишь один файл, в котором содержится весь код.

require('rb.php');

Весьма просто, разве нет? Заглянем внутрь и обнаружим, что файл данных весит 260 кбайт и представляет собой собранную из множества файлов версию фреймворка. На GitHub можно скачать обычную версию из примерно 40 файлов. В этом случае для подключения необходим следующий код:

require('redbean.inc.php');

Структура


Фреймворк имеет хорошо организованную структуру классов. В состав входят драйвера для PDO и Oracle. Это значит что RedBeanPHP поддерживает широкий спектр баз данных. В документации указывается поддержка следующих баз данных: MySQL 5, SQLite, PostgreSQL, CUBRID, Oracle. Поддержка последней не входит в rb.php, её надо загружать из GitHub отдельно. Впрочем нам никто не мешает написать собственный драйвер, унаследовав от класса RedBean_Driver.
А для того, чтобы модифицировать внутреннюю логику работы фреймвока, необходимо создать свою версию QueryWriter. Данную тему я затрону детально в третьей части обзора.
Фреймворк поддерживает Plugins, и имеет простенький Logger класс для отображения всех запросов фреймворка к базе на экран. Запись в файл я не нашел, но никаких проблем не представляет унаследовать собственный класс.
Вот код логгера идущего в поставке:

class RedBean_Logger_Default implements RedBean_Logger {
  public function log() {
    if (func_num_args() > 0) {
      foreach (func_get_args() as $argument) {
        if (is_array($argument)) echo print_r($argument,true); else echo $argument;
		echo "<br>\n";
      }
    }
  }
}

Все файлы отлично документированы с использованием PHPDocs. Подсказки в IDE прекрасно работают, за исключением полей объекта связанных с столбцами таблицы.

Использование


Для подключения необходимо указать строку формата PDO:

R::setup('mysql:host=localhost;dbname=mydatabase',
        'user','password'); //mysql

R::setup('pgsql:host=localhost;dbname=mydatabase',
        'user','password'); //postgresql

R::setup('sqlite:/tmp/dbfile.txt',
        'user','password'); //sqlite

Пример создания новой записи и новой таблицы в случае её отсуствия (только в fluid режиме):

// Создаем объект (bean) работающий с таблицей book
$book = R::dispense( 'book' );

// выставляем значение полей, тип поля будет автоматически модифицирован в зависимости от значения
$book->title = 'Gifted Programmers';
$book->author = 'Charles Xavier'; 
$book->price = 99.99; 
//Сохраняем, первичный ключ id создается автоматически
$id = R::store($book);

А так загружается существующий “bean”.

$book = R::load('book', $id);

Автор называет bean’ми объекты (бин — англ. боб растений), содержащие данные. Данный объект не является моделью и имеет целью просто хранение данных и работа с записями в базе. Как с ними связать модель содержащую бизнес-логику я расскажу во второй части обзора.

Фреймворк по наличию поля id у объекта решает создать или обновить запись. Как изменить эту логику я покажу в третьей части.

Поиск bean происходит посредством задания WHERE выражения:

$needles = R::find('needle',
        ' haystack = :haystack ORDER BY :sortorder', 
            array( 
                ':sortorder'=>$sortorder, 
                ':haystack'=>$haystack 
            )
        );


Там же, во втором параметре, вы можете указать ORDER BY и LIMIT конструкции.

Связи


Работа с реляционной базой данных предполагает наличие связанности между таблицами. RedBeanPHP поддерживает все необходимые типы связей. Для создания связи один-ко-многим мы используем свойство с префиксом own.

// Создаем bean работающий с таблицей village
$village = R::dispense('village');
// Создаем bean’ы работающие с таблицей building
list($mill,$tavern) = R::dispense('building',2);
    
// для связывания просто присвойте массив bean в поле с префиксом own
$village->ownBuilding = array($mill,$tavern);
     
R::store($village);    

В базе данных cоздастся таблица building. В таблицу building будет добавлено поле village_id, указывающее на поле id таблицы village. Так же будет создана связь в родительской таблицу и созданы необходимые индексы. Связь будет иметь свойство каскадного обновления и удаления данных.

Если посмотреть с другой стороны связи, то возможно обращение к родительскому объекту таким образом:

$village = $mill->village

RedBeanPHP поддерживает концепцию “ленивой загрузки”. То есть загрузка связанных данных произойдет в момент обращения к полю объекта.

// запроса к таблице building не происходит 
$village = R::load('village',$id);
// осуществляется запрос к building
$buildings = $village->ownBuilding; 

Own поле является ассоциативным массивом, поэтому мы может заменить отдельный объект:

$village->ownBuilding[$id] = $house;

Что бы удалить связь между объектами необходимо вызвать следующий код (сами объекты удалены не будет):

unset($village->ownBuilding[$someID]); 
R::store($village);

Для удаления bean и связанных с ним родительских объектов:

R::trash( $book );
// или в случае массива
R::trashAll( $books );

Для очистки таблицы:

R::wipe( 'book' );

Для создания связи многие-ко-многим используем свойство с префиксом shared.

$army = R::dispense('army');
$village->sharedArmy[] = $army;
$village2->sharedArmy[] = $army;

В результате будет создана дополнительная таблица army_village и будут созданы нужны связи и индексы. В остальном работа с shared списками аналогична own списком.

Обычно на тип bean’а указывает имя поля объекта.

// $village отображает таблицу village
$village = $building->village    

Но иногда необходимо присвоить bean полям объекта не следующим этому правилу.

list($teacher,$student) = R::dispense('person',2);
$project->student = $student; 
$project->teacher = $teacher; 

При сохранении фреймфок руководствуется типом bean указаным в dispence, и оба дочерних объекта будут сохранены в таблице person, а в таблице project будут созданы поля student_id и teacher_id.
В таком случае при выборке bean из базы может произойти не однозначная ситуация, так как фреймворк не может определить какой тип находиться в полях $project->student и $project->teacher. В данном случае указание типа осуществляется посредством вызова fetchAs:

$teacher = $project->fetchAs('person')->teacher;

Деревья

Фреймворк поддерживает кольцевые ссылки один-ко-многим, многие-ко-многим.
Например создание дерева категорий:

//$subbean и $bean имеют тип category
$subbean = R::dispense('category');
$subbean->title = $title;
$subbean->url = $url;
$subbean->parent = $bean;
R::store($subbean);

В таблице будет создано поле parent_id и необходимые связи и индексы.

Быстродействие


Фреймфорк имеет 2 режима работы: fluide и frozen. Во fluide режиме RedBeanPHP сам позаботиться о структуре вашей таблицы, принеся в жертву быстродействие. После завершения разработки увеличить производительность можно переведя фреймворк с режим frozen:

R::freeze( true ); 

Запросы к базе


ORM поддерживает SQL запросы.

R::exec( 'update page set title="test" where id=1' );

Так же присуствует построитель запросов:

R::$f->begin()
    ->select('*')->from('bean')
    ->where(' field1 = ? ')->put('a')->get('row');


Краткий обзор моделей


Забегу немного во вторую часть и опишу как содается модель в RedBeanPHP.

В начале вы работаете с bean, без привлечения моделей. Когда вам нужна модель, вы создаете класс вида Model_BeanName, например Model_User, и там описываете дополнительную логику. Связывание bean и модели происходит автоматически.

$user = R::dispence('user');
$user->modelMethod();

То есть, начинаем с ActiveRecord, и при необходимости расширяемся до моделей. Автор называет этот подход FUSE (англ. сплавка).

Во второй части мы мы продолжим разговор о функциях фреймворка для ежедневной работы:
  • Модели
  • Транзакции
  • и другие темы

В третей части обсудим дополнительные возможности RedBeanPHP:
  • Отладка
  • Сервер для Javascript приложения.
  • Плагины
  • Практический пример изменения внутренней логики
  • и другие темы


Ресурсы


Официальный сайт RedBeanPHP
Поделиться публикацией

Похожие публикации

Комментарии 47

    0
    Расскажите еще какие возможно фишки 5.3 или 5.4 используются если они есть, а то пока что единственный интересный момент который я увидел это работа со связами — crud, конечно стартовый файл в 260Кб это по-моему перебор. И опять же странно почему нет такой простой вещи как namespace, вроде уже «немодно» давно писать MyClass_Is_Here. А так ничего интересного как-то, давно был обзор netty вот он поинтересней оказался немного.
    +1
    … принеся в жертву быстродействие..

    Вот это очень настораживает. Переключение режимов это хорошо, но есть ли реальные цифры сравнения или тесты? (желательно для двух режимов)
      0
      Таких тестов я не проводил. Некоторая информация есть на сайте RedBean Performance

      Сам автор верит, ORM работает настолько быстро, насколько правильно вы позволяете это ей делать.
        0
        Я ответил вам ниже.
          0
          Нет, здесь нет никаких проблем. После R::freeze(true); остаются лишь нужные для вставки/чтения/апдейтов запросы, ничего лишнего. Другое дело, что это всегда чтение строки целиком и такая же запись.

          Без фриза он постоянно использует describre, show tables и прочее. Это нужно ему для изменения схемы БД на ходу.
          0
          Данная статья только первая в цикле из 3 частей, описыващая только самые базовые вещи.

          " И опять же странно почему нет такой простой вещи как namespace, вроде уже «немодно» давно писать MyClass_Is_Here"

          Тут я отвечу переводом раздела PHP 5.3 namespaces на официфльном сайте.

          «Я не люблю PHP namespaces, они имеют некоторые проблем. Есть ряд причин почему я не использую namespace в RedBeanPHP.

          Я хочу оставить совместимость с 5.2.
          Я не хочу заставлять пользователей использовать namespace, схема с синголтоном R::doSomething() достаточно мощная
          Хотя я ценю работу команды PHP, я считаю выбор символа \ ошибкой. Это как эхо MSDOS в PHP.
          Мне кажется, чем меньше синтаксических конструкций, тем лучше.

            +1
            эхо MSDOS
            В Windows оно до сих пор так…
              +3
              Не использовать namespace и фишки 5.3-5.4 даже в будущем это ппц. Думаю проект не выживет, т.к. ничего нового он не показал, все как-то сделано «для себя», ну и конечно объяснения вида «не буду делать потому что не хочу и не нравиться» это конечно круто. Идею с «bean» я видел в sugarCRM там сделана получше конечно эта идея(про код молчу). 5.2 Уже не используется наверное год как, ну не поддерживается, поэтому думаю данный фв так и останется поделкой для себя, увы и ах.
                0
                >5.2 Уже не используется наверное год как
                С чего бы? Все резко переписали старые приложения? Или помолясь обновили софт на старых серверах?
                  +1
                  Я сказал не поддерживается (не разрабатывается дальше), посмотрите на php.net, не надо передергивать мои слова, ясно?
                    +1
                    Ясно, сэр.
                +3
                Мне кажется автору ОРМа, надо бы выполнить var_dump(get_declared_classes()) с использованием неймспейсов и без, чтобы понять, что неймспейсы это не просто синтаксическая конструкция.

                А еще статичный вызов функций не просто не «мощный», а отстойный, потому что добавляет статической связанности компонентам системы.
                  +2
                  >А еще статичный вызов функций не просто не «мощный», а отстойный, потому что добавляет статической связанности компонентам системы.

                  +миллиард

                  заебали со своими синглтонами.
                –1
                будет разделено на 2 расти

                Опечатка в первом абзаце.
                  0
                  Только в первом?) Автор, лови в личку (письмо, — не подумайте).
                  • НЛО прилетело и опубликовало эту надпись здесь
                  0
                  Очень милая ОРМочка, но, увы, неприменимая иначе как для развлечения.

                  Из принятой в ней схемы работы «прочитали запись целиком, апдейтнули запись целиком» следует две проблемы:
                  1. Избыточность запросов и трафика: нельзя обновить один столбец, можно обновить лишь строку целиком. При большой нагрузке это становится довольно ощутимым питфоллом.
                  2. Race conditions. Это полный ахтунг, комментарии излишни, в общем-то.
                    0
                    Дополнил статью:

                    ORM поддерживает SQL запросы.

                    R::exec( 'update page set title=«test» where id=1' );

                    Так же присуствует построитель запросов:

                    R::$f->begin()
                    ->select('*')->from('bean')
                    ->where(' field1 =? ')->put('a')->get('row');

                    Возможно это будет ответом на ваше замечание.
                    +1
                    Я чего-то не понял, или это ActiveRecord?

                      0
                      Это полноценный ORM.
                        0
                        en.wikipedia.org/wiki/Active_record_pattern — ну ORM, реализующий паттерн ActiveRecord.
                          0
                          В этом смысле ORM подмножество ActiveRecord.

                          Из приведенной ссылки:
                          This pattern is commonly used by object persistence tools, and in object-relational mapping (ORM).

                          Данный паттерн используеться средствами управления объектами, и ORM.
                        0
                        Формально это не ActiveRecord, в прошлом обсуждении на хабре признали DAO
                          0
                          В комментарии выше Mox предоставил ссылку, из которой следует что ORM реализует паттер ActiveRecord.

                          http://en.wikipedia.org/wiki/Active_record_pattern

                          http://habrahabr.ru/post/150023/#comment_5079253
                            0
                            В качестве флуда: то, что AR используется в ORM, не говорит о том, что он используется во всех ORM. Есть ORM и на других паттернах, та же Doctrine 2 например.
                            Из ссылки на вики:
                            The interface of an object conforming to this pattern would include functions such as Insert, Update, and Delete…
                            Насколько я знаю, здесь метода для INSERT/UPDATE/DELETE не принадлежат самому объекту, поэтому это и не Active Record.
                          0
                          Больше смахивает на DataMapper + Repository (на статических методах).
                          0
                          Очередной велосипед( Все фишки уже умеют все ORM'ы. Что нового?
                            0
                            Есть ли ORM в которой создание новой таблицы и новой записи сводиться к нескольким строкам?

                            require('rb.php');
                            $book = R::dispense( 'book' );
                            R::setup('mysql:host=localhost;dbname=mydatabase', 'user','password');
                            $book->title = 'Gifted Programmers';
                            $book->author = 'Charles Xavier';
                            $id = R::store($book);
                              –2
                              и че store каждый проверяет наличие таблицы book? И если нет, то создает таблицу по указанным полям?

                              Да, такого нет, ибо это бред!
                                +1
                                Проверяет или нет зависит от режима работы — fluid или frozen.
                            0
                            Нечто очень похожее: j4mie.github.com/idiormandparis/
                            0
                            Пример из приведенного фреймфорка:

                            class User extends Model {
                            }

                            $user = Model::factory('User')
                            ->where_equal('username', 'j4mie')
                            ->find_one();
                            $user->first_name = 'Jamie';
                            $user->save();

                            Аналогичный функционал на RedBeanPHP

                            $user = R::findRow('user',' username =? ',
                            array( 'j4mie' )
                            );
                            $user->first_name = 'Jamie';
                            R::store($user);

                            Как мы видим, в RedBeanPHP можно не описывать класс модели. О том, как создаются модели в RedBeanPHP я опишу в второй части.
                              0
                              В idiormandparis мы можем отказать от моделей так же, но в этом случае мы работаем с ActiveRecord.
                                0
                                Лично для меня использование моделей это скорее плюс, чем минус. Проще добавляются фильтры и связи. Визуально код воспринимается лучше.
                                  0
                                  Забегу вперед и опишу как содается модель в RedBeanPHP.

                                  В начале вы работаете с bean, без привлечения моделей. Когда вам нужна модель, вы создаете класс вида Model_BeanName, в нашем случае Model_User, и там описываете дополнительную логику. Связывание bean и модели происходит автоматически.
                                  То есть:

                                  $user = R::dispence('user');
                                  $user->modelMethod();

                                  То есть, начинаем с ActiveRecord, и при необходимости расширяемся до моделей. Мы всегда работаем по одной схеме.
                              0
                              Даёшь ту же фишку в виде специального режима работы AcriveRecord в Yii!
                                0
                                Ходят слуши что в Yii 2 будет нечто подобное.
                                  0
                                  Какую ту же фишку?
                                    0
                                    Создание структуры БД на лету исходя из обращения к данным в коде.
                                      0
                                      Что-то не помню чтобы на конфе 19 мая такое говорили, можно пруф?
                                        0
                                        Вы топик читали?
                                          0
                                          Да, меня интересует пруф на вот это:
                                          >>Ходят слуши что в Yii 2 будет нечто подобное.
                                          где об этом говорили и кто? бабки у подъезда чтоли?
                                            0
                                            Вы задали мне вопрос «Какую ту же фишку? », а на самом деле хотели спросить у olegl84 про пруф?
                                            Сформулируйте мысль точно и передайте её правильному адресату пожалуйста.
                                    +1
                                    Можете предложить в специальном разделе форума, если хочется видеть эту фичу: www.yiiframework.com/forum/index.php/forum/42-design-discussions-for-yii-20/

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое