Builder
ORM (Object-Relational Mapping) - это технология, которая позволяет взаимодействовать с файлами, используя объектно-ориентированный подход. С помощью такой ORM можно работать с данными, хранящимися в файлах, как если бы они были записями в реляционной базе данных.
ORM предоставляет удобный интерфейс для чтения и записи информации в файлы. Она обеспечивает автоматическую генерацию запросов для работы с файлами, что упрощает процесс создания, изменения и удаления данных. Благодаря этому, процесс работы со структурами файловой системы становится более простым и интуитивно понятным.
Основные преимущества использования файловой ORM включают в себя:
- Упрощение процесса работы с файлами;
- Улучшение скорости доступа к данным в файловой системе;
- Уменьшение количества необходимых действий при работе с файлами;
- Повышение надежности и безопасности работы с файлами;
Структура данных CSV совместима, но с некоторыми изменения для более быстрой работы
Возможности
- Модели
- Поиск по первичному ключу
- Поиск (where)
- Поиск (whereIn)
- Поиск (whereNotIn)
- Поиск (orWhere)
- Limit - Offset
- Возврат структуры файла
- Возврат количества записей в файле
- Возврат информации о существовании записи
- Сортировка строк
- Создание записей
- Обновление записей
- Удаление записей
- Очистка файла
- Частичный поиск (Like)
- Нестрогий поиск (Lax)
- Приведение типов (Casts)
- Условия запросов (Scope)
- Динамические условия
- Условные выражения (Conditional clauses)
- Связи
- Связь один к одному
- Связь один ко многим
- Связь многие ко многим
- Жадная загрузка
Работы с изменениями в файле, в том числе и вставка выполняется с блокировкой файла для защиты от случайного удаления данных в случае если несколько пользователей одновременно пишут в файл
Первых столбец в файле считается уникальным
Может быть строковым и числовым
Если столбец строковой, то все вставки должны быть с уже заданным уникальным ключом
Если столбец числовой, то уникальный ключ будет генерироваться автоматически
Может быть строковым и числовым
Если столбец строковой, то все вставки должны быть с уже заданным уникальным ключом
Если столбец числовой, то уникальный ключ будет генерироваться автоматически
Запросы
Все запросы проводятся через модели в котором должен быть указан путь к файлу с данными В самих моделях могут быть реализованы дополнительные методы
Модель
Все запросы выполняются через модель, для начала работы необходимо создать класс модели в котором нужно указать путь к таблице<?php use MotorORM\Builder; class TestModel extends Builder { public string $table = __DIR__ . '/test.csv'; }Также в базовом классе можно указать директорию с таблицами, а в самом классе модели достаточно указать имя файла
<?php namespace App\Models; use MotorORM\Builder; class Model extends Builder { protected ?string $tableDir = __DIR__ . '/../../storage/database'; } class User extends Model { protected string $table = 'users.csv'; }
Поиск по первичному ключу
TestModel::query()->find(1); # Если первичный ключ строковой TestModel::query()->find('value');
Поиск по заданным значениям
# Find by name limit 1 TestModel::query()->where('name', 'Миша')->limit(1)->get(); # Find by name and first 1 TestModel::query()->where('name', 'Миша')->first(); # Find by name and title TestModel::query()->where('name', 'Миша')->where('title', 'Заголовок10')->get(); # Get from condition TestModel::query()->where('time', '>=', 1231231235)->get(); # Get first line TestModel::query()->first();
Поиск (whereIn)
TestModel::query()->whereIn('id', [1, 3, 4, 7])->get();
Поиск (whereNotIn)
TestModel::query()->whereNotIn('id', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->get();
Поиск (orWhere)
# Get records by multiple conditions TestModel::query() ->where(function(Builder $builder) { $builder->where('name', 'Миша'); $builder->orWhere(function(Builder $builder) { $builder->where('name', 'Петя'); $builder->where('title', '<>', ''); }); }) ->get();
Limit - offset
# Get first 3 lines TestModel::query()->limit(3)->get(); # Get lines 1 - 10 $lines = TestModel::query()->offset(0)->limit(10)->get(); # Get last 10 records $lines = TestModel::query()->orderByDesc('created_at')->offset(0)->limit(10)->get();
Получение количества записей
TestModel::query()->where('time', '>', 1231231234)->count();
Проверка существования записи
TestModel::query()->where('field', 'value')->exists();
Сортировка записей
# Сортировка TestModel::query()->orderByDesc('created_at')->limit(3)->get(); # Двойная сортировка (time desc, id asc) Test::query() ->where('name', 'Миша') ->orderByDesc('time') ->orderBy('id') ->limit(3) ->get();
Получение колонок
TestModel::query()->headers();
Создание записей
TestModel::query()->create(['name' => 'Миша']);
Обновление записей
# Обновление полей TestModel::query()->where('name', 'Миша')->update(['text' => 'Новый текст']); # Сохранение записи $test = TestModel::query()->where('name', 'Миша')->first(); $test->text = 'Новый текст'; $test->save(); # Обновление полей $testModel = TestModel::query()->find(17); $affectedLines = $testModel->update(['text' => 'Новый текст']);
Удаление записей
# Удаление записи TestModel::query()->where('name', 'Миша')->delete(); # Удаление записей в цикле $records = TestModel::query()->get(); foreach($records as $record) { $record->delete(); }
Очистка таблицы
# Truncate file TestModel::query()->truncate();
Частичный поиск (Like)
Поиск по частичному совпадению// Строки начинающиеся на hi $test = TestModel::query()->where('tag', 'like', 'hi%')->get(); // Строки заканчивающиеся на hi $test = TestModel::query()->where('tag', 'like', '%hi')->get(); // Строки содержащие hi $test = TestModel::query()->where('tag', 'like', '%hi%')->get(); // Этот запрос эквивалентен запросу выше $test = TestModel::query()->where('tag', 'like', 'hi')->get();
Нестрогий поиск (Lax)
Поиск по нестрогому совпадению При поиске orm использует строгое сравнение, чтобы задействовать нестрогий режим, можно использовать lax// Будут найдено первое совпадение NAME, name, namE, Name итд $user = User::query()->where('login', 'lax', 'name')->first();
Приведение типов (Casts)
По умолчанию все поля полученные из файла строковые За некоторыми исключениями Поле primary key - int Поля заканчивающиеся на _id и _at - int Пустые поля - null Для переопределения используйте свойство castsclass Story extends Model { protected array $casts = [ 'rating' => 'int', 'reads' => 'int', 'locked' => 'bool', ]; }Поддерживаются следующие типы
'int', 'integer' => int 'real', 'float', 'double' => float 'string' => string 'bool', 'boolean' => bool 'object' => json_decode($value, false), 'array' => json_decode($value, true),
Условия запросов (Scope)
Каждый scope — это обычный метод, который начинается с префикса scope. Именно по префиксу ORM понимает, что это scope. Внутрь scope передаётся запрос, на который можно навешивать дополнительные условия.class Story extends Model { public function scopeActive(Builder $query): Builder { return $query->where('active', true); } }Использование:
Story::query() ->active() ->paginate($perPage);
Динамические условия
Некоторые scope зависят от параметров, передающихся в процессе составления запроса. Для этого достаточно описать эти параметры внутри scope после параметра $query:class Story extends Model { public function scopeOfType(Builder $query, string $type): Builder { return $query->where('type', $type); } }Использование:
Story::query() ->ofType('new') ->paginate($perPage);
Условные выражения (Conditional clauses)
Иногда вам может понадобиться, чтобы определенный запроса выполнялся на основе другого условия. Например, вы можете захотеть применить where оператор только в том случае, если заданное входное значение присутствует во входящем HTTP-запросе. Вы можете сделать это, используя when метод:$active = true; $stories = Story::query() ->when($active, function (Story $query, $active) { $query->where('active', $active); }) ->get();Метод when выполняет данное замыкание только тогда, когда первый аргумент равен true. Если первый аргумент равен false, замыкание не будет выполнено. Вы можете передать другое замыкание в качестве третьего аргумента when метода. Это замыкание будет выполняться только в том случае, если первый аргумент оценивается как false. Чтобы проиллюстрировать, как можно использовать эту функцию, мы будем использовать ее для настройки порядка запросов по умолчанию:
$sortByVotes = 'sort_by_votes'; $users = Story::query() ->when($sortByVotes, function ($query, $sortByVotes) { $query->orderBy('votes'); }, function ($query) { $query->orderBy('name'); }) ->get();
Связи (Relations)
В данный момент поддерживается 3 вида связей hasOne - один к одному hasMany - один ко многим hasManyThrough - многие ко многимОдин к одному (hasOne)
3 параметра, имя класса, внешний и внутренний ключ Внешний и внутренний ключ определяются автоматически, за исключением когда имена полей не совпадают с именем класса или если связь обратная belongsTo (Возможно в будущем это будет реализовано)// Прямая связь class User extends Model { public function story(): Builder { return $this->hasOne(Story::class); } }
// Обратная связь class Story extends Model { public function user(): Builder { return $this->hasOne(User::class, 'id', 'user_id'); } }
Один ко многим (hasMany)
3 параметра, имя класса, внешний и внутренний ключ Внешний и внутренний ключ определяются автоматически, за исключением когда имена полей не совпадают с именем классаclass Story extends Model { public function comments(): Builder { return $this->hasMany(Comment::class); } }
Многие ко многим (hasManyThrough)
5 параметров, имя конечного класса, имя промежуточного класса, внешние и внутренние ключи Внешние и внутренние ключи определяются автоматически, за исключением когда имена полей не совпадают с именами классовclass Story extends Model { public function tags(): Builder { return $this->hasManyThrough(Tag::class, TagStory::class); } }
Жадная загрузка (Eager load)
По умолчанию все связи с ленивой загрузкой (lazy load) Связь не будет загружена до тех пор, пока явно не будет вызвана
Для того чтобы жадно загрузить данные необходимо вызвать метод with и передать имена связей, которые требуется жадно загрузить
class StoryRepository implements RepositoryInterface { public function getStories(int $perPage): CollectionPaginate { return Story::query() ->orderByDesc('locked') ->orderByDesc('created_at') ->with(['user', 'comments']) ->paginate($perPage); } } }Жадная загрузка извлекает данные используя всего несколько запросов. Это позволяет избежать проблемы N + 1. Представьте, что у вас есть этот код, который находит 10 сообщений, а затем отображает имя автора каждого сообщения.
foreach ($storyRepository->getStories(10) as $story) { echo $story->user->login; }
Без ленивой загрузки при каждой итерации цикла было бы обращение в файловую систему для получения данных, то есть 1 запрос на получение списка постов и 10 на получение пользователей
Жадная загрузка избавляет от этой проблемы, 1 запрос на получение списка постов и 1 на получение пользователей этих постов