visavi/motor-orm

v4.2.2 2023-11-28 09:02 UTC

README

此脚本提供面向对象的方法来处理存储在文件系统中的文本数据

数据结构CSV兼容,但为了更快的运行做了一些修改

功能

构建器

  • 通过唯一键搜索
  • 通过任何指定条件搜索
  • 返回文件结构
  • 返回文件中的记录数量
  • 返回记录是否存在的信息
  • 排序行
  • 将行写入文件,并生成自增键
  • 根据任何条件更新记录
  • 根据任何条件删除记录
  • 类型转换(Casts)
  • 作用域
  • 清理文件
  • 贪婪加载
  • 一对一关系
  • 一对多关系
  • 多对多关系

集合

  • 将集合转换为数组
  • 获取第一个记录
  • 获取最后一个记录
  • 获取集合中的记录数量
  • 向集合中添加记录
  • 从集合中删除记录
  • 在集合中设置值
  • 检查集合是否为空
  • 清理集合
  • 切片集合
  • 遍历集合,获取键和值

集合分页

  • 扩展Collection类
  • 获取当前页
  • 获取页数数量
  • 获取页面数组

迁移

处理文件中的更改,包括插入操作,使用文件锁定来防止多个用户同时写入文件时意外删除数据

文件中的第一列被视为唯一键

可以是字符串或数字

如果列是字符串,则所有插入操作都必须具有已定义的唯一键

如果列是数字,则唯一键将自动生成

查询

所有查询都通过模型进行,其中必须指定数据文件的路径。模型本身可以实现额外的方法。

示例

# Create class
use MotorORM\Builder;

class TestModel extends Builder
{
    public string $table = __DIR__ . '/test.csv';
}

# Find by primary key
TestModel::query()->find(1);

# 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 by condition in
TestModel::query()->whereIn('id', [1, 3, 4, 7])->get();

# Get by condition not in
TestModel::query()->whereNotIn('id', [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->get();

# Get records by multiple conditions and pagination
TestModel::query()
    ->where(function(Builder $builder) {
        $builder->where('name', 'Миша');
        $builder->orWhere(function(Builder $builder) {
            $builder->where('name', 'Петя');
            $builder->where('title', '<>', '');
        });
    })
    ->paginate(10);

# Get count
TestModel::query()->where('time', '>', 1231231234)->count();

# 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();

# Get headers
TestModel::query()->headers();

# Get first line
TestModel::query()->first();

# Get first 3 lines
TestModel::query()->limit(3)->get();

# Get last 3 lines
TestModel::query()->orderByDesc('created_at')->limit(3)->get();

# Find by name and double sort (time desc, id asc)
Test::query()
    ->where('name', 'Миша')
    ->orderByDesc('time')
    ->orderBy('id')
    ->limit(3)
    ->get();

# Create string
TestModel::query()->create(['name' => 'Миша']);

# Update strings
TestModel::query()->where('name', 'Миша')->update(['text' => 'Новый текст']);

# Update string
$test = TestModel::query()->where('name', 'Миша')->first();
$test->text = 'Новый текст';
$test->save();

# Update strings
$testModel = TestModel::query()->find(17);
$affectedLines = $testModel->update(['text' => 'Новый текст']);

# Delete records
TestModel::query()->where('name', 'Миша')->delete();

# Delete records
$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)

默认情况下,从文件获取的所有字段都是字符串类型

但有例外

  • 主键字段 - int
  • 以_id和_at结尾的字段 - int
  • 空字段 - null

要重写,请使用casts属性

class 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)

有时您可能需要根据其他条件执行特定查询。例如,您可能只想在输入值存在于输入HTTP请求的情况下应用WHERE运算符。您可以使用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个参数,类名,外键和内键

外键和内键自动定义,除非字段名称与类名不匹配或反向关系belongs_to(可能在未来实现)

// Прямая связь
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)

默认情况下,所有关系都使用懒加载

关系只有在显式调用时才会加载

为了贪婪加载数据,需要调用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个获取这些帖子用户的查询

迁移

要调用迁移类,需要将所需模型传递给构造函数

$migration = new Migration(new Test());

创建表

创建表,例如创建具有五个字段的test.csv文件

$migration->createTable(function (Migration $table) {
    $table->create('id');
    $table->create('title');
    $table->create('text');
    $table->create('user_id');
    $table->create('created_at');
});

删除表

$migration->deleteTable();

创建列

$migration->changeTable(function (Migration $table) {
    // Создаст колонку text c текстом по умолчанию "Текст" после колонки title
    $table->create('text')->default('Текст')->after('title'); 
    
    // Создаст колонку test перед колонкой id
    $table->create('test')->before('id'); 
});

重命名列

$migration->changeTable(function (Migration $table) {
    // Переименует user_id в author_id
    $table->rename('user_id', 'author_id'); 
});

删除列

$migration->changeTable(function (Migration $table) {
    // Удалит колонку title
    $table->delete('title');
});

检查表/列是否存在

// Проверит существование таблицы
$migration->hasTable();

// Проверит существование колонки
$migration->hasColumn('field');