Еще одна реализация Django ORM на PHP. На текущий момент находится в стадии активной доработки и не
рекомендуется к использованию в production
до версии 1.0., (*2)
Приносим извинения за неполноту документации. Документация находится разработке., (*3)
Часть кода основана на yii2 фреймворке, но ввиду монолитности ядра мы были вынуждены
"распилить" фреймворк по отдельным пакетам. В Mindy ORM
активно используется Query из yii2., (*4)
Поддерживаемые типы субд: sqlite
, mysql
, pgsql
, mssql
(все то, что умеет yii2 query).
Теоретически ORM способна работать с NoSql
хранилищами за счет реализации собственного менеджера
с переопределением Lookup
'ов., (*5)
Опишем подключение к бд:, (*6)
Model::setConnection([ 'sqlite::memory' ]);
Опишем модель:, (*7)
class MyModel extends Model { public function getFields() { return [ // Ключ - название поля при обращении к модели 'name' => [ // Тип поля 'class' => CharField::className(), // Длинна поля 'length' => 100, // NULL|NOT NULL 'null' => false, // "Читабельное" имя модели. Используется в Mindy\Form\ModelForm // для построения форм автоматически. 'verboseName' => 'Продукция', // Пример валидаторов 'validators' => [ function ($value) { if (mb_strlen($value, 'UTF-8') < 3) { return "Minimal length < 3"; } return true; }, new UniqueValidator ] ], 'price' => [ 'class' => CharField::className(), // Значение по умолчанию 'default' => 0 ], 'description' => [ 'class' => TextField::className(), 'default' => '' ], 'category' => [ 'class' => ForeignField::className(), 'modelClass' => Category::className(), 'null' => true ], 'lists' => [ 'class' => ManyToManyField::className(), 'modelClass' => ProductList::className() ] ]; } }
Описание модели завершено. Валидация происходит на основе валидации полей модели. Модель описана, теперь необходимо создать ее в субд. Для этого выполним следующий код:, (*8)
$sync = new Sync([ new MyModel() ]); $sync->create();
В бд создатутся таблицы всех переданных моделей а так же индексы и связи если это возможно., (*9)
Создадим несколько записей:, (*10)
// Если модель с идентичными полями найдется, то ORM вернет ее, иначе создаст. $model = MyModel::objects()->getOrCreate(['name' => 'Поросенок петр', 'price' => 1]); $modelTwo = new MyModel; // Массовое присвоение аттрибутов $modelTwo->setData([ 'name' => 'Рубаха', 'price' => 2 ]); // Валидация и сохранение if($modelTwo->isValid() && $modelTwo->save()) { echo 'Модель сохранена'; } $modelThree = new MyModel; $modelThree->name = 'Джинсы'; $modelThree->price = 3; // Валидация и сохранение if($modelThree->isValid() && $modelThree->save()) { echo 'Модель сохранена'; }
Выборки реализованы по аналогии с Django Framework:, (*11)
// SELECT * FROM my_model WHERE price >= 2 $models = MyModel::objects()->filter(['price__gte' => 2])->all(); // SELECT * FROM my_model WHERE name = 'Рубаха' $models = MyModel::objects()->filter(['name' => 'Рубаха'])->all(); // SELECT * FROM my_model WHERE id IN (1, 2, 3) $models = MyModel::objects()->filter(['pk__in' => [1, 2, 3]])->all();
И так далее. Более подробную информацию смотрите в разделе Lookups, (*12)
Очистим базу данных:, (*13)
$sync = new Sync([ new MyModel() ]); $sync->delete();
Менеджер это интерфейс проксирующий до класса QuerySet
, который занимается обработкой
наших Lookup
'ов и выполнением запросов. Каждая модель по умолчанию имеет менеджер objects()
., (*14)
User::objects()
Менеджер обрабатывает QuerySet. Основным методом менеджера является getQuerySet()
.
Это возвращает объект, который впоследствии обрабатывается менеджером.
Если вы хотите изменить логику менеджера по умолчанию, вы можете создать свой собственный менеджер.
Например, activeUsersManager()
:, (*15)
class ActiveUsersManager extends Manager { public function getQuerySet() { $qs = parent::getQuerySet(); return $qs->filter(['status' => User::STATUS_ACTIVE]); } }
Модель с собственным менеджером:, (*16)
class User extends Model { public function getFields() { 'name' => [ 'class' => CharField::className(), ], 'status' => [ 'class' => BooleanField::className(), 'default' => false ] } public static function activeUsersManager($instance = null) { $className = get_called_class(); return new ActiveUsersManager($instance ? $instance : new $className); } }
Теперь вы можете использовать ActiveUsersManager()
с помощью User::activeUsers()
.
И все запросы будут выполняться в соответствии с вашей логикой обработки только активных пользователей.
Например, этот код поможет выбрать активных пользователей имя которых начинается с 'A':, (*17)
User::activeUsers()->filter(['name__startswith' => 'A'])->all();
Выборка одного объекта. В случае если объектов соответствующих условию больше 1, выбрасываем исключение, иначе возвращаем объект. Если объектов не найдено, то возвращаем null., (*18)
Найдем объект где pk == 5
., (*19)
$model = User::objects()->filter(['pk' => 5])->get();
Возвращает массив моделей класса Model
или ассоциативный массив в случае если
вызван метод asArray()
., (*20)
Выборка всех пользователей. Вернется массив моделей класса User
., (*21)
User::objects()->all();
Выборка всех пользователей. Вернется ассоциативный массив., (*22)
User::objects()->asArray()->all();
Возвращает число объектов подходящих под условия выборки, (*23)
User::objects()->count();
Вы можете вызывать эти методы последовательно:, (*24)
User::objects()->filter(['name' => 'Max'])->exclude(['address' => 'New York'])->order(['-address'])->all();
Данный код возвращает всех пользователей с именем Max
живущих не в New York
с сортировкой
по адресу в обратном порядке., (*25)
SELECT * FROM user WHERE name='Max' AND address != 'New York' ORDER BY address DESC
Вы можете найти всех пользователей состоящих в группе с именем example
, используя следующий код:, (*26)
User::objects()->filter(['group__name' => 'example'])->all();
group
в данном случае связь m2m
до модели Group
, (*27)
Вы можете найти всех пользователей которые не состоят в группе с названием example
, используя следующий код:, (*28)
User::objects()->exclude(['group__name' => 'users'])->all();
group
в данном случае связь m2m
до модели Group
, (*29)
Поиск всех пользователей и сортировка по имени:, (*30)
User::objects()->order(['name'])->all();
Поиск всех пользователей и сортировка по имени в обратном порядке:, (*31)
User::objects()->order(['-name'])->all();
Поиск всех пользователей и сортировка в случайном порядке, (*32)
User::objects()->order(['?'])->all();
С помощью лукапов(lookups) вы можете фильтровать QuerySet.
Лукапы применяются в методах менеджера exclude()
и filter()
и передаются в них массивом,
где ключом массива являются поля (с лукапами), а значением - значение, по которому и производится фильтрация.
Пример лукапа:, (*33)
'name__exact'
Данный лукап указывает на то, что поле name
должно быть равно указанному значению. Пример применения:, (*34)
User::objects()->filter(['name__exact' => 'Max'])->all();
Выбираем всех пользователей имя которых равно 'Max'.
На самом деле, лукап exact
является лукапом "по умолчанию". То есть, в данном примере можно было было обойтись условием filter(['name' => 'Max'])
., (*35)
SELECT * FROM user WHERE name = 'Max'
Лукап, применяющийся для поиска значений NULL
в базе данных.
Пример применения:, (*36)
User::objects()->filter(['name__isnull' => true])->all();
Произойдет выбор всех пользователей cо значением имени в БД NULL
., (*37)
SELECT * FROM user WHERE name IS NULL
Если передать false
в качестве значения то sql запрос будет выглядеть слудющим
образом:, (*38)
SELECT * FROM user WHERE name IS NOT NULL
Лукапы, применяющиеся для поиска значений меньше заданного (lt
), и меньших либо равных заданному (lte
)
Пример lt
лукапа:, (*39)
Product::objects()->filter(['price__lt' => 100.00])->all();
Произойдет выбор всех продуктов с ценой строго меньшей 100.00
., (*40)
Пример lte
лукапа:, (*41)
Product::objects()->filter(['price__lte' => 100.00])->all();
$dateTime = new DateTime(); $dateTime->setTimestamp(strtotime('+15 days')) Product::objects()->filter(['created_at__lte' => $dateTime])->all();
Произойдет выбор всех продуктов с ценой меньшей, либо равной 100.00
., (*42)
lt
формирует слудющий sql:, (*43)
SELECT * FROM user WHERE price < 100.00
lte
формирует слудющий sql:, (*44)
SELECT * FROM user WHERE price <= 100.00
Лукапы, применяющиеся для поиска значений больше заданного (gt
), и больших либо равных заданному (lte
)
Пример gt
лукапа:, (*45)
Product::objects()->filter(['price__gt' => 100.00])->all();
Произойдет выбор всех продуктов с ценой строго большей 100.00
., (*46)
Пример gte
лукапа:, (*47)
Product::objects()->filter(['price__gte' => 100.00])->all();
Произойдет выбор всех продуктов с ценой большей, либо равной 100.00
., (*48)
gt
формирует слудющий sql:, (*49)
SELECT * FROM user WHERE price > 100.00
gte
формирует слудющий sql:, (*50)
SELECT * FROM user WHERE price >= 100.00
Применяется для поиска значний строго равных заданному. Пример:, (*51)
User::objects()->filter(['name__exact' => 'Max'])->all();
Произойдет выбор всех пользователей с именем 'Max'.
** Лукап является лукапом по умолчанию, то есть в предыдущем примере можно указать просто ['name' => 'Max']
**, (*52)
Применяется для поиска значений в которых присутствует заданноу значение (аналог - SQL LIKE).
Регистрозависимый поиск. Для регистронезависимого используйте lookup icontains
.
Пример:, (*53)
User::objects()->filter(['name__contains' => 'ax'])->all();
Произойдет выбор всех пользователей, в имени которых есть сочетание 'ax'., (*54)
SELECT * FROM user WHERE name LIKE '%ax%'
Полностью повторяет lookup contains
, но осуществляет регистронезависимый поиск., (*55)
Применяется для поиска значений, начинающихся с заданного значения:
Регистрозависимый. Для регистронезависимого используйте lookup istartswith
.
Пример:, (*56)
User::objects()->filter(['name__startswith' => 'M'])->all();
Произойдет выбор всех пользователей, имя которых начинается с 'M'., (*57)
SELECT * FROM user WHERE name LIKE 'M%'
Полностью повторяет lookup startswith
, но осуществляет регистронезависимый поиск., (*58)
Применяется для поиска значний, заканчивающихся заданным значением:
Регистрозависимый. Для регистронезависимого используйте lookup iendswith
.
Пример:, (*59)
User::objects()->filter(['name__endswith' => 'on'])->all();
Произойдет выбор всех пользователей, имя которых начинается с 'on'., (*60)
SELECT * FROM user WHERE name LIKE '%on'
Полностью повторяет lookup iendswith
, но осуществляет регистронезависимый поиск., (*61)
Применяется для поиска значений, попадающих в список переданных значений: Пример:, (*62)
User::objects()->filter(['pk__in' => [1, 2]])->all();
Произойдет выбор всех пользователей, pk которых попадают в список [1, 2]
., (*63)
SELECT * FROM user WHERE id IN (1, 2)
Lookup может принимать QuerySet в качестве значения, (*64)
Данный лукап позволяет принимать не только массив значений, но и объект QuerySet. В этом случае в запрос будет добавлен подзапрос., (*65)
Пример:, (*66)
$group_qs = Group::objects()->filter(['name__startswith' => 'A']); $users = User::objects()->filter(['groups__pk__in' => $group_qs->select('id') ])->all();
Произойдет выбор всех пользователей, имена групп которых начинаются с 'A'. Будет выполнен всего один запрос на выбор пользователей., (*67)
Данный лукап позволяет найти значения, расположенные между переданных значений. Данный лукап принимает массив из двух элементов., (*68)
Пример:, (*69)
Product::objects()->filter(['price__range' => [10, 20]])
Произойдет выбор всех продуктов с ценой от 10 до 20., (*70)
SELECT * FROM product WHERE price BETWEEN 10 AND 20
Данный лукап работает только с полями типа DateTimeField
, DateField
.
Позволяет найти все значения, расположенные в заданном году., (*71)
Пример:, (*72)
Product::objects()->filter(['date_added__year' => 2014])
Произойдет выбор всех продуктов, добавленных в 2014 году., (*73)
Внимание: sql запрос будет отличаться для разных субд., (*74)
Данный лукап работает только с полями типа DateTimeField
, DateField
.
Позволяет найти все значения, расположенные в заданном месяце., (*75)
Пример:, (*76)
Product::objects()->filter(['date_added__year' => 12])
Произойдет выбор всех продуктов, добавленных в декабре., (*77)
Внимание: sql запрос будет отличаться для разных субд., (*78)
Данный лукап работает только с полями типа DateTimeField
, DateField
.
Позволяет найти все значения, расположенные в заданном дне месяца., (*79)
Пример:, (*80)
Product::objects()->filter(['date_added__day' => 25])
Произойдет выбор всех продуктов, добавленных 25 числа любого месяца., (*81)
Внимание: sql запрос будет отличаться для разных субд., (*82)
Данный лукап работает только с полями типа DateTimeField
, DateField
.
Позволяет найти все значения, расположенные в заданном дне недели., (*83)
Значения: 1 - Воскресенье, 2 - Понедельник, ..., 7 - Суббота. Порядок дней определяет ORM и подстраивает под текущую субд по следующей причине:, (*84)
Method Range ------ ----- PYTHON datetime_object.weekday() 0-6 Sunday=6 datetime_object.isoweekday() 1-7 Sunday=7 dt_object.isoweekday() % 7 0-6 Sunday=0 # Can easily add 1 for a 1-7 week where Sunday=1 MYSQL DAYOFWEEK(timestamp) 1-7 Sunday=1 WEEKDAY(timestamp) 0-6 Monday=0 POSTGRES EXTRACT('dow' FROM timestamp) 0-6 Sunday=0 TO_CHAR(timestamp, 'D') 1-7 Sunday=1 ORACLE TO_CHAR(timestamp, 'D') 1-7 Sunday=1 (US), Sunday=6 (UK)
Пример:, (*85)
Product::objects()->filter(['date_added__week_day' => 2])
Произойдет выбор всех продуктов, добавленных в понедельник., (*86)
Внимание: sql запрос будет отличаться для разных субд., (*87)
Данный лукап работает только с полями типа DateTimeField
, TimeField
.
Позволяет найти все значения, расположенные в заданном часе.
Пример:, (*88)
Product::objects()->filter(['date_added__hour' => 10])
Произойдет выбор всех продуктов, добавленных в 10 часов., (*89)
Внимание: sql запрос будет отличаться для разных субд., (*90)
Данный лукап работает только с полями типа DateTimeField
, TimeField
.
Позволяет найти все значения, расположенные в заданной минуте.
Пример:, (*91)
Product::objects()->filter(['date_added__minute' => 35])
Произойдет выбор всех продуктов, добавленных в 35 минут., (*92)
Внимание: sql запрос будет отличаться для разных субд., (*93)
Данный лукап работает только с полями типа DateTimeField
, TimeField
.
Позволяет найти все значения, расположенные в заданной секунде.
Пример:, (*94)
Product::objects()->filter(['date_added__minute' => 45])
Произойдет выбор всех продуктов, добавленных в 45 секунд., (*95)
Внимание: sql запрос будет отличаться для разных субд., (*96)
** Не реализовано **, (*97)
Поиск по регулярному выражению.
Регистрозависимый. Для регистронезависимого используйте lookup iregex
.
Пример:, (*98)
Product::objects()->filter(['name__regex' => '[a-z]'])
Произойдет выбор всех продуктов, соответствующих регулярному выражению [a-z]
, (*99)
Внимание: sql запрос будет отличаться для разных субд., (*100)
Полностью повторяет предыдущий lookup, но осуществляет регистронезависимый поиск., (*101)
Внимание: sql запрос будет отличаться для разных субд., (*102)
Q-объекты необходимы для удобного формирования условий выборки. Существует 2 Q-объекта: OrQ и AndQ., (*103)
Q-объект формируется следующим образом:, (*104)
new OrQ([['status' => 1, 'user_id' => 1],['status' => 2, 'user_id' => 4]]);
И затем его можно передать в методы filter или exclude:, (*105)
Requests::objects()->filter([new OrQ([['status' => 1, 'user_id' => 1],['status' => 2, 'user_id' => 4]])])->all()
Данный запрос выберет нам все заявки со статусом 1 от пользователя с id равным 1 и заявки со статусом 2 от пользователя с id равным 4., (*106)