{"id":21762828,"url":"https://github.com/visavi/motor-orm","last_synced_at":"2026-05-21T07:04:14.794Z","repository":{"id":45668497,"uuid":"456001789","full_name":"visavi/motor-orm","owner":"visavi","description":null,"archived":false,"fork":false,"pushed_at":"2023-11-28T09:03:52.000Z","size":128,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-26T01:24:57.258Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/visavi.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-02-05T22:34:20.000Z","updated_at":"2024-06-27T17:28:23.000Z","dependencies_parsed_at":"2023-11-28T10:26:52.067Z","dependency_job_id":"102a1116-9f38-4759-a53c-3bac75783cc3","html_url":"https://github.com/visavi/motor-orm","commit_stats":null,"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Fmotor-orm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Fmotor-orm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Fmotor-orm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/visavi%2Fmotor-orm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/visavi","download_url":"https://codeload.github.com/visavi/motor-orm/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244739940,"owners_count":20501990,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-26T12:13:15.992Z","updated_at":"2026-05-21T07:04:09.771Z","avatar_url":"https://github.com/visavi.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Motor ORM\n\nДанный скрипт предоставляет ООП подход для работы текстовыми данными сохраненными в файловой системе\n\nСтруктура данных CSV совместима, но с некоторыми изменения для более быстрой работы\n\n## Возможности\n\n### Builder\n- Поиск по уникальному ключу\n- Поиск по любым заданным условиям\n- Возврат структуры файла\n- Возврат количества записей в файле\n- Возврат информации о существовании записи\n- Сортировка строк\n- Запись строки в файл с генерацией автоинкрементного ключа\n- Обновление записей по любым условиям\n- Удаление записей по любым условиям\n- Приведение типов (Casts)\n- Scope\n- Очистка файла\n- Жадная загрузка\n- Связь один к одному\n- Связь один ко многим\n- Связь многие ко многим\n\n\n### Collection\n- Преобразование коллекции в массив\n- Получение первой записи\n- Получение последней записи\n- Получение количества записей в коллекции\n- Добавление записи в коллекцию\n- Удаление записи из коллекции\n- Установка значения в коллекции\n- Проверка коллекции на пустоту\n- Очистка коллекции\n- Срез коллекции\n- Обход с получением ключа и значения из коллекции\n\n### Collection Paginate\n- Расширяет класс Collection\n- Получение текущей странице\n- Получение количества страниц\n- Получение массива со страницами\n\n### Migration\n- [Создание таблицы](#Создание-таблицы)\n- [Удаление таблицы](#Удаление-таблицы)\n- [Создание колонок](#Создание-колонок)\n- [Переименовывание колонок](#Переименовывание-колонок)\n- [Удаление колонок](#Удаление-колонок)\n- [Проверка существования](#Проверка-существования-таблицыколонки)\n\nРаботы с изменениями в файле, в том числе и вставка выполняется с блокировкой файла для защиты от случайного удаления данных в случае если несколько пользователей одновременно пишут в файл\n\nПервых столбец в файле считается уникальным\n\nМожет быть строковым и числовым\n\nЕсли столбец строковой, то все вставки должны быть с уже заданным уникальным ключом\n\nЕсли столбец числовой, то уникальный ключ будет генерироваться автоматически\n\n## Запросы\n\nВсе запросы проводятся через модели в котором должен быть указан путь к файлу с данными\nВ самих моделях могут быть реализованы дополнительные методы\n\n## Примеры\n\n```php\n\n# Create class\nuse MotorORM\\Builder;\n\nclass TestModel extends Builder\n{\n    public string $table = __DIR__ . '/test.csv';\n}\n\n# Find by primary key\nTestModel::query()-\u003efind(1);\n\n# Find by name limit 1\nTestModel::query()-\u003ewhere('name', 'Миша')-\u003elimit(1)-\u003eget();\n\n# Find by name and first 1\nTestModel::query()-\u003ewhere('name', 'Миша')-\u003efirst();\n\n# Find by name and title\nTestModel::query()-\u003ewhere('name', 'Миша')-\u003ewhere('title', 'Заголовок10')-\u003eget();\n\n# Get from condition\nTestModel::query()-\u003ewhere('time', '\u003e=', 1231231235)-\u003eget();\n\n# Get by condition in\nTestModel::query()-\u003ewhereIn('id', [1, 3, 4, 7])-\u003eget();\n\n# Get by condition not in\nTestModel::query()-\u003ewhereNotIn('id', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])-\u003eget();\n\n# Get records by multiple conditions and pagination\nTestModel::query()\n    -\u003ewhere(function(Builder $builder) {\n        $builder-\u003ewhere('name', 'Миша');\n        $builder-\u003eorWhere(function(Builder $builder) {\n            $builder-\u003ewhere('name', 'Петя');\n            $builder-\u003ewhere('title', '\u003c\u003e', '');\n        });\n    })\n    -\u003epaginate(10);\n\n# Get count\nTestModel::query()-\u003ewhere('time', '\u003e', 1231231234)-\u003ecount();\n\n# Get lines 1 - 10\n$lines = TestModel::query()-\u003eoffset(0)-\u003elimit(10)-\u003eget();\n\n# Get last 10 records\n$lines = TestModel::query()-\u003eorderByDesc('created_at')-\u003eoffset(0)-\u003elimit(10)-\u003eget();\n\n# Get headers\nTestModel::query()-\u003eheaders();\n\n# Get first line\nTestModel::query()-\u003efirst();\n\n# Get first 3 lines\nTestModel::query()-\u003elimit(3)-\u003eget();\n\n# Get last 3 lines\nTestModel::query()-\u003eorderByDesc('created_at')-\u003elimit(3)-\u003eget();\n\n# Find by name and double sort (time desc, id asc)\nTest::query()\n    -\u003ewhere('name', 'Миша')\n    -\u003eorderByDesc('time')\n    -\u003eorderBy('id')\n    -\u003elimit(3)\n    -\u003eget();\n\n# Create string\nTestModel::query()-\u003ecreate(['name' =\u003e 'Миша']);\n\n# Update strings\nTestModel::query()-\u003ewhere('name', 'Миша')-\u003eupdate(['text' =\u003e 'Новый текст']);\n\n# Update string\n$test = TestModel::query()-\u003ewhere('name', 'Миша')-\u003efirst();\n$test-\u003etext = 'Новый текст';\n$test-\u003esave();\n\n# Update strings\n$testModel = TestModel::query()-\u003efind(17);\n$affectedLines = $testModel-\u003eupdate(['text' =\u003e 'Новый текст']);\n\n# Delete records\nTestModel::query()-\u003ewhere('name', 'Миша')-\u003edelete();\n\n# Delete records\n$records = TestModel::query()-\u003eget();\nforeach($records as $record) {\n    $record-\u003edelete();\n}\n\n# Truncate file\nTestModel::query()-\u003etruncate();\n```\n\n### Частичный поиск (Like)\nПоиск по частичному совпадению\n\n```php\n// Строки начинающиеся на hi\n$test = TestModel::query()-\u003ewhere('tag', 'like', 'hi%')-\u003eget();\n\n// Строки заканчивающиеся на hi\n$test = TestModel::query()-\u003ewhere('tag', 'like', '%hi')-\u003eget();\n\n// Строки содержащие hi\n$test = TestModel::query()-\u003ewhere('tag', 'like', '%hi%')-\u003eget();\n\n// Этот запрос эквивалентен запросу выше\n$test = TestModel::query()-\u003ewhere('tag', 'like', 'hi')-\u003eget();\n```\n\n### Нестрогий поиск (Lax)\nПоиск по нестрогому совпадению\n\nПри поиске orm использует строгое сравнение, чтобы задействовать нестрогий режим, можно использовать lax\n```php\n// Будут найдено первое совпадение NAME, name, namE, Name итд\n$user = User::query()-\u003ewhere('login', 'lax', 'name')-\u003efirst();\n```\n\n### Приведение типов (Casts)\nПо умолчанию все поля полученные из файла строковые\n\nЗа некоторыми исключениями\n- Поле primary key - int\n- Поля заканчивающиеся на _id и _at - int\n- Пустые поля - null\n\nДля переопределения используйте свойство casts\n\n```php\nclass Story extends Model\n{\n    protected array $casts = [\n        'rating' =\u003e 'int',\n        'reads'  =\u003e 'int',\n        'locked' =\u003e 'bool',\n    ];\n}\n```\nПоддерживаются следующие типы\n- 'int', 'integer' =\u003e int\n- 'real', 'float', 'double' =\u003e float\n- 'string' =\u003e string\n- 'bool', 'boolean' =\u003e bool\n- 'object' =\u003e json_decode($value, false),\n- 'array' =\u003e json_decode($value, true),\n\n### Условия запросов (Scope)\n\nКаждый scope — это обычный метод, который начинается с префикса scope. Именно по префиксу ORM понимает, что это scope. Внутрь scope передаётся запрос, на который можно навешивать дополнительные условия.\n\n```php\nclass Story extends Model\n{\n    public function scopeActive(Builder $query): Builder\n    {\n        return $query-\u003ewhere('active', true);\n    }\n}\n```\n\nИспользование:\n\n```php\nStory::query()\n    -\u003eactive()\n    -\u003epaginate($perPage);\n```\n\n#### Динамические условия\nНекоторые scope зависят от параметров, передающихся в процессе составления запроса. Для этого достаточно описать эти параметры внутри scope после параметра $query:\n\n```php\nclass Story extends Model\n{\n    public function scopeOfType(Builder $query, string $type): Builder\n    {\n        return $query-\u003ewhere('type', $type);\n    }\n}\n```\n\nИспользование:\n```php\nStory::query()\n    -\u003eofType('new')\n    -\u003epaginate($perPage);\n```\n\n### Условные выражения (Conditional clauses)\nИногда вам может понадобиться, чтобы определенный запроса выполнялся на основе другого условия. Например, вы можете захотеть применить where оператор только в том случае, если заданное входное значение присутствует во входящем HTTP-запросе. Вы можете сделать это, используя when метод:\n\n```php\n$active = true;\n\n$stories = Story::query()\n    -\u003ewhen($active, function (Story $query, $active) {\n        $query-\u003ewhere('active', $active);\n    })\n    -\u003eget();\n```\n\nМетод when выполняет данное замыкание только тогда, когда первый аргумент равен true. Если первый аргумент равен false, замыкание не будет выполнено.\n\nВы можете передать другое замыкание в качестве третьего аргумента when метода. Это замыкание будет выполняться только в том случае, если первый аргумент оценивается как false. Чтобы проиллюстрировать, как можно использовать эту функцию, мы будем использовать ее для настройки порядка запросов по умолчанию:\n\n```php\n$sortByVotes = 'sort_by_votes';\n\n$users = Story::query()\n    -\u003ewhen($sortByVotes, function ($query, $sortByVotes) {\n        $query-\u003eorderBy('votes');\n    }, function ($query) {\n        $query-\u003eorderBy('name');\n    })\n    -\u003eget();\n```\n\n### Связи (Relations)\nВ данный момент поддерживается 3 вида связей\n- hasOne - один к одному\n- hasMany - один ко многим\n- hasManyThrough - многие ко многим\n\n#### Один к одному (hasOne)\n3 параметра, имя класса, внешний и внутренний ключ\n\nВнешний и внутренний ключ определяются автоматически, за исключением когда имена полей не совпадают с именем класса или если связь обратная belongsTo (Возможно в будущем это будет реализовано)\n```php\n// Прямая связь\nclass User extends Model\n{\n    public function story(): Builder\n    {\n        return $this-\u003ehasOne(Story::class);\n    }\n}\n\n// Обратная связь\nclass Story extends Model\n{\n    \n    public function user(): Builder\n    {\n        return $this-\u003ehasOne(User::class, 'id', 'user_id');\n    }\n}\n```\n\n#### Один ко многим (hasMany)\n3 параметра, имя класса, внешний и внутренний ключ\n\nВнешний и внутренний ключ определяются автоматически, за исключением когда имена полей не совпадают с именем класса\n```php\nclass Story extends Model\n{\n    public function comments(): Builder\n    {\n        return $this-\u003ehasMany(Comment::class);\n    }\n}\n```\n\n#### Многие ко многим (hasManyThrough)\n5 параметров, имя конечного класса, имя промежуточного класса, внешние и внутренние ключи\n\nВнешние и внутренние ключи определяются автоматически, за исключением когда имена полей не совпадают с именами классов\n```php\nclass Story extends Model\n{\n    public function tags(): Builder\n    {\n        return $this-\u003ehasManyThrough(Tag::class, TagStory::class);\n    }\n}\n```\n\n### Жадная загрузка (Eager load)\nПо умолчанию все связи с ленивой загрузкой (lazy load)\n\nСвязь не будет загружена до тех пор, пока явно не будет вызвана\n\nДля того чтобы жадно загрузить данные необходимо вызвать метод with и передать имена связей, которые требуется жадно загрузить\n\n```php\nclass StoryRepository implements RepositoryInterface\n\n    public function getStories(int $perPage): CollectionPaginate\n    {\n        return Story::query()\n            -\u003eorderByDesc('locked')\n            -\u003eorderByDesc('created_at')\n            -\u003ewith(['user', 'comments'])\n            -\u003epaginate($perPage);\n    }\n}\n```\n\nЖадная загрузка извлекает данные используя всего несколько запросов. Это позволяет избежать проблемы N + 1.\n\nПредставьте, что у вас есть этот код, который находит 10 сообщений, а затем отображает имя автора каждого сообщения.\n\n```php\nforeach ($storyRepository-\u003egetStories(10) as $story) {\n    echo $story-\u003euser-\u003elogin;\n}\n```\n\nБез ленивой загрузки при каждой итерации цикла было бы обращение в файловую систему для получения данных, то есть 1 запрос на получение списка постов и 10 на получение пользователей\n\nЖадная загрузка избавляет от этой проблемы, 1 запрос на получение списка постов и 1 на получение пользователей этих постов\n\n### Миграции\n\nДля вызова класса миграции необходимо в конструктор передать нужную нам модель\n```php\n$migration = new Migration(new Test());\n```\n\n#### Создание таблицы\nСоздание таблицы, пример создания файла test.csv с пятью полями\n\n```php\n$migration-\u003ecreateTable(function (Migration $table) {\n    $table-\u003ecreate('id');\n    $table-\u003ecreate('title');\n    $table-\u003ecreate('text');\n    $table-\u003ecreate('user_id');\n    $table-\u003ecreate('created_at');\n});\n```\n\n#### Удаление таблицы\n```php\n$migration-\u003edeleteTable();\n```\n\n#### Создание колонок\n```php\n$migration-\u003echangeTable(function (Migration $table) {\n    // Создаст колонку text c текстом по умолчанию \"Текст\" после колонки title\n    $table-\u003ecreate('text')-\u003edefault('Текст')-\u003eafter('title'); \n    \n    // Создаст колонку test перед колонкой id\n    $table-\u003ecreate('test')-\u003ebefore('id'); \n});\n```\n\n#### Переименовывание колонок\n```php\n$migration-\u003echangeTable(function (Migration $table) {\n    // Переименует user_id в author_id\n    $table-\u003erename('user_id', 'author_id'); \n});\n```\n\n#### Удаление колонок\n```php\n$migration-\u003echangeTable(function (Migration $table) {\n    // Удалит колонку title\n    $table-\u003edelete('title');\n});\n```\n\n#### Проверка существования таблицы/колонки\n```php\n// Проверит существование таблицы\n$migration-\u003ehasTable();\n\n// Проверит существование колонки\n$migration-\u003ehasColumn('field');\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvisavi%2Fmotor-orm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvisavi%2Fmotor-orm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvisavi%2Fmotor-orm/lists"}