ORM RedBeanPHP
RedBeanPHP это отличное решение для первичного этапа разработки - прототипирования. Также, этот ORM вполне можно использовать на собственных закрытых проектах и не требовательных к обширной бизнес логики проектах. RedBeanPHP поддерживает расширение моделей кастомными методами и предоставляет SQL Builder. Но!
Тот же SQL Builder я нахожу крайне убогим, по мере обрастания уровня BL кастомным функционалом - вся прелесть простого экспорта и "жадной предзагрузки" сходят на нет, валидацию нужно прикручивать самому, менеджеры коллекций тоже отсутствуют (хотя в том же Eloquent от Laravel их тоже нет).
В общем на вкус и цвет..) Но это отличная (если не единственная) ORM подходящая для быстрого прототипирования схемы БД и приложения.
Установка
Автор рекомендует использовать для установки phar-архив, поскольку он содержит основные плагины и прост в обращении.
Внимание!
Автор отказывается предоставлять возможность установить RedBean через Composer начиная с версии 4.0, обсуждение сабжа.
Дабы не возится с алиасами секции use .. as ..
в каждом контроллере:
use RedBean_Facade as R;
Используйте ф-цию class_alias:
class_alias('RedBean_Facade', 'R');
SQL Builder
Пример построения запроса с помощью SQL Builder (Mixing SQL and PHP):
// getNew() создает новый инстанс SQL Builder'а
$sub_builder = R::$f->getNew()->begin()
->select('count(id)')
->from('field')
->where('preset_id = preset.id AND is_filterable = ?')->put(true)
->limit(1);
// begin() - включает режим захвата для всех вызовов `R::$f->`
R::$f->begin()->select('*');
R::$f->addSQL(',') // нужно явно добавлять запятую :(
->open() // открыть скобку
->nest($sub_builder)
->close() // закрыть скобку
->addSQL(' as fields_count1') // .. и вот таким макаром дописывать алиас :(
// Добавление аналогичного запроса без использования Builder'а
->addSQL(',')
->open()
->addSQL('SELECT count(id) FROM field WHERE preset_id = preset.id')
->close()
->addSQL(' as fields_count2')
->from('preset')
->where() // Добавляет ключевое WHERE без условия
//->where('created_at > ?')->put('2013')
->open()
->addSQL('created_at > ? OR updated_at > ?')->put('2013')->put('2013')
->or('updated_at > ?')->put('2013')
->close()
->and('name LIKE ?')->put('%ы');
$rows = R::$f->get(); // 'row|col|cell'
Внимание!
Этот SQL Builder не контролирует последовательность добавления выражений! Поэтому вы не можете добавить условие ->where()
после того, как применили ->limit()
.
Для себя я решил вообще не использовать режим SQL Builder'а - проще уже составлять ручками нативный SQL запрос. Последовательность выражений не контролирует, дополнение кода в chain методах теряется, синтаксис больше запутывает и увеличивает объем кода. Однако, тогда возникает проблема экранирования.. Поскольку, mysql_real_escape_string($string)
уберут из PHP 5.5, a mysqli_real_escape_string($link, $string)
требует $link
(ресурс соединения с БД).
Получение данных
Получение записи по ID
$bean = R::load('post', $id); // RedBean_OODBBean
if ($bean->id) { /* объект получен */ }
Методы и условия поиска
$nean = R::findOne('user',' email = ?', ['user@gmail.com']); // RedBean_OODBBean|null
$bean = R::findOrDispense('user','id = ?', [33]); // RedBean_OODBBean
$beans = R::find('user', 'WHERE email LIKE ?', ['%com']); // RedBean_OODBBean[]
$beans = R::findAll('user', 'WHERE email = ?', ['user@gmail.com']); // RedBean_OODBBean[]
$rows = R::findAndExport($entity, ...); // array
$rows = R::getAll('SELECT id, email FROM user LIMIT ?', [10]); // array
$row = R::getRow('SELECT * FROM user WHERE email LIKE ?', ['%gmail.com']); // array
$rows = R::getAssoc('SELECT * FROM user WHERE email LIKE ?', ['%gmail.com']); // assoc
#Array (
# [1] => user1@gmail.com
# [2] => user2@gmail.com
#)
$beans = R::find('user','email = ?', ['user@gmail.com']); // RedBean_OODBBean[]
// SELECT * FROM `user` WHERE 1 ORDER BY email
$beans = R::findAll('user', 'WHERE email = ?', ['user@gmail.com']); // RedBean_OODBBean[]
// SELECT * FROM `user` WHERE email = 'user@gmail.com'
Сбросить индексы массива
Массив результирующих записей содержит PK ID в качестве ключей. Если вам необходимо сбросить индексацию - есть простой прием:
$beans = array_values($beans);
Условия и сортировка при получении связанных записей:
$beans = $owner->withCondition(' archived_at IS NULL ')->with(' ORDER BY name ASC ')->ownWish;
Получить кастомные поля:
$table_name = R::getCell('SHOW TABLES LIKE ?', [$entity]);
Обновление
$params = array('name' => 'New name');
$bean = R::load($entity, $id);
$bean->import($params);
$id = R::store($bean); // int
Удаление
R::trash($bean); // null
Relations
Many-to-One
Назначить пользователю роль:
$role = R::dispense('role');
$role->name = 'Role 1';
$role->ownUser[] = $user; // user.role_id
R::store($role);
Для получения пользователя по группе также используется префикс own
:, но чтобы получить группу пользователя, достаточно просто обратится к свойству group:
$user = current($role->ownUser);
$role = $user->group;
Many-to-Many
Назначить пользователю несколько ролей:
$user = R::load('user', 1);
$role = R::load('role', 2);
$user->sharedRole[] = $role; // создается таблица role_user. Почему не user_role?
R::store($user);
echo $user->countShared('role'); // 1 связанная запись
Примечание
Метод shared*
в отличие от link
исключает дублирование связей в одной таблице. То есть, если вы добавили связь user_id = 1
, role_id = 2
- то при следующем добавлении такой же связи действие будет проигнорировано. Однако, метод shared*
не позволяет сохранять дополнительные данные в связующую таблицу.
Связать записи с указанием таблицы и дополнительных данных:
// Все руки не доходят..
Получить связанные записи (указывать связующую таблицу не обязательно):
$roles = $user->sharedRole; // RedBean_OODBBean[]
$roles = $user->via('user_role')->sharedRole;
$roles = $user->via('user_role')->withCondition('field > ?', ['val'])->sharedRole;
# >>
# SELECT * FROM user WHERE (id IN (1));
# SELECT
# role.*, role_user.user_id AS linked_by
# FROM role_user
# INNER JOIN role ON (role.id = role_user.role_id AND role_user.user_id IN (1))
# WHERE created_at > '1384363629'
Получить связанные записи с дополнительным условием фильтрации:
Прочее
Получить связующий bean
:
$links = $user->withCondition("user_role.role_id = ?", [2])->ownUserRole;
$link = current($links);
$link->updated_at = date('Y-m-d H:i:s');
R::store($link); // вы можете также сохранять bean связующей таблицы
Конвертирование, импорт, экспорт
Важно!
Если вы сконвертируете bean в массив и добавите массив данных в новое свойство - у вас отвалится export() этого bean'а.
Импорт
Конвертировать массивы в коллекцию bean'ов:
$rows = R::getAll($sql);
$users = R::convertToBeans('user', $rows);
Экспорт
Экспортировать все связанные записи одного bean
или для массива бобов:
$filters = ['bean', 'bean2']; // имена таблиц
R::exportAll($bean, false, $filters);
Примечание
Метод exportAll()
экспортирует все связанные записи, и лишь потом фильтрует их. При этом фильтр работает только по имени таблицы, не зависимо от промежуточной связи.
Метод экспортирует только найденные бобы в ассоциативный массив:
$row = R::findAndExport($entity, 'id=?', [33]);
if (isset($row['id']) && $row['id']) { ... }
Preload (Lazy load)
Ленивая загрузка и экспорт (конвертирование bean'ов в массив) данных:
$preset = R::load('preset', 3);
R::preload($preset, 'ownProduct|product,ownProduct.ownAttr|attr');
$rows = R::beansToArray([$preset]);
Примечание
Почему-то у меня не работала ленивая загрузка, при указании имени бинов без расширенного описания, как-то: 'product,product.attr'
. При этом, наличие FK ключей (индексов) никак не влияет на результат.
Поиск с ленивой загрузкой и трансформацией в массив:
$beans = R::findAll($entity, $query);
$beans = array_values($beans);
$beans = $this->loadRelatedObjects($entity, $beans, 'ownOptions|options');
$result = R::beansToArray($beans);
Хелперы
Определить имя связующей таблицы:
$table = R::getCell('SHOW TABLES LIKE ?', ['user_role']);
$relation_name = $table ? $table : 'user_table';
Метаданные
R::renameAssociation(); // how to use?
$bean->getMeta('that this?') // how to use?
#orm, #redbean