efabrica/nette-repository

Nette 数据库的仓库

0.4.4 2024-07-22 09:00 UTC

README

此扩展通过提供类型提示实体(ActiveRows)、查询(Selections)和仓库(服务)来增强您 Nette 数据库的静态分析。

安装

您可以使用 Composer(PHP 的依赖管理器)安装此扩展。

composer require efabrica/nette-repository

要启用扩展,您需要将其注册到配置中

extensions:
    netteRepo: Efabrica\NetteRepository\Bridge\EfabricaNetteRepositoryExtension

最后,您可以运行仓库代码生成命令以生成必要的类和文件

$ php vendor/bin/enc

用法

实体

Entity 类是 ActiveRow 的子类,作为您实体的超类。您可以使用我们的代码生成工具自动创建它,或者手动编写。

插入实体

assert($repository instanceof PersonRepository);
assert($entity instanceof Person);
$entity = $repository->create();
$entity->name = 'John';
$entity->surname = 'Doe';
$entity->age = 42;
$entity->save();

保存数组

$entity = $repository->insertOne([
    Person::NAME => 'John',
    Person::SURNAME => 'Doe',
    Person::AGE => 42,
]); // always returns entity

// More verbose approach:
$entity = $repository->create();
$entity->fill([
    Person::NAME => 'John',
    Person::SURNAME => 'Doe',
    Person::AGE => 42,
]);
$entity->save();

经典

$person = $repository->insert([
    Person::NAME => 'John',
    Person::SURNAME => 'Doe',
    Person::AGE => 42,
]); // always returns Entity

多插入

$persons = [];
foreach (range(30, 40) as $age) {
    $person = $repository->createRow();
    Person::NAME => 'John',
    Person::SURNAME => 'Doe',
    Person::AGE => $age,
    $persons[] = $person;
}
$repository->insertMany($persons); // always returns int

更新实体

$entity = $repository->find($id);
$entity->name = 'Jake';
$entity->update(); // or $entity->save();

经典

$repository->update($id, [Person::name => 'Jake']);

删除实体

$repository->delete($id);

或者,如果您已经有了实体

$entity = $repository->find($id);
$entity->delete();

作用域

作用域是一个定义了为 Repository、Query 和 Entity 禁用哪些现有行为的类。

活动作用域从 Repository 传递到 Query,再从 Query 传递到 Entity。

->withScope(Scope $scope) 返回应用了给定作用域的对象的副本。

->scopeRaw() 返回应用了原始作用域的对象的副本。原始作用域移除所有行为。

->scopeFull() 返回应用了完整作用域的对象的副本。完整作用域保留所有行为。这是默认作用域,除非您在仓库的 setup() 方法中更改作用域。

示例

final class AdminScope implements \Efabrica\NetteRepository\Repository\Scope\Scope
{
    public function apply(RepositoryBehaviors $behaviors, Repository $repository): void
    {
        // Remove these behaviors because they are not needed for the Admin
        $behaviors
            ->remove(SoftDeleteBehavior::class)
            ->remove(PublishBehavior::class);
        
        // Do not add any new behaviors here, because this scope can be used by different repositories
        // and you might introduce unwanted side effects.
        
        // However, you can conditionally add behaviors based on the repository type and some parameter
        // For example, if you want to apply a special behavior for admin users in the user repository, you can do this:
        if ($repository instanceof UserRepository && $someContainerParameter) { // Scopes can be services and receive parameters.
            $behaviors->add(AdminBehavior::class); // This is a hypothetical behavior, just for illustration.
        }
    }
}

要使用作用域作为 容器服务(在某些情况下可能不是必需的),请按照以下步骤操作

use Efabrica\NetteRepository\Repository\RepositoryBehaviors;
abstract class RepositoryBase extends \Efabrica\NetteRepository\Repository\Repository
{
    /** @inject */
    public AdminScope $adminScope;
    
    public function scopeAdmin(): self
    {
        return $this->withScope($this->adminScope);
    }
    
    // Do this if you want to set the AdminScope as default:
    protected function setup(RepositoryBehaviors $behaviors) : void 
    {
        $behaviors->setScope($this->adminScope);
    }
}

并且 可选 实现查询的简写方法

use YourBeautifulApplication\Admin\AdminScope;

abstract class QueryBase extends \Efabrica\NetteRepository\Repository\Query
{
    public function scopeAdmin(): self
    {
        // This method returns a copy of the query with your own Admin scope applied
        // The Admin scope removes some behaviors that are not relevant for the Admin
        return $this->withScope($this->repository->adminScope);
        // Alternatively, if the Admin scope does not depend on any parameters, you can create a new instance of it like this:
        return $this->withScope(new AdminScope());
    }
}

用法

// all of these are equivalent:
$repository->findBy(['age > ?' => 18])->scopeAdmin()->fetchAll();
$repository->scopeAdmin()->findBy(['age > ?' => 18])->fetchAll();
$repository->query()->where('age > ?', 18)->scopeAdmin()->fetchAll();
$repository->scopeAdmin()->query()->where('age > ?', 18)->fetchAll();
$repository->query()->scopeAdmin()->where('age > ?', 18)->fetchAll();

代码生成器

代码生成是可选的,但建议使用它。

要运行代码生成,请使用此命令

$ php bin/console efabrica:nette-repo:code-gen
# OR
$ php bin/console e:n:c
# OR 
$ php vendor/bin/enc

对于数据库中的每个表,它将在 /Generated/ 命名空间中生成以下类:(例如:person 表)

  • Repository\Generated\Repository\PersonRepositoryBase - 仓库基类,包含 PersonQueryPerson 实体的类型提示。(抽象)
  • Repository\Generated\Query\PersonQuery - 查询类,包含 Person 实体的类型提示。(抽象)
  • Repository\Generated\Entity\Person - 实体类,包含列的类型和列名称的公共常量。(最终)

当您运行代码生成器时,这些类将始终重新生成。它们不应手动修改。

对于数据库中的每个表,它还会生成以下类 /Generated/ 命名空间之外,但仅当它们不存在时。如果它们存在,则不会覆盖。

  • Repository\PersonRepository - 扩展 PersonRepositoryBase。在这里编写您自定义的仓库方法。(最终)
  • Repository\Query\PersonQuery - 扩展 PersonQuery。在这里编写您自定义的查询方法。(最终)
  • Repository\Entity\PersonBody - 插入到 Person 实体的特质。在这里编写您自定义的实体方法。(特质)

当您运行代码生成器时,这些类 不会重新生成。它们旨在由您自定义。如果您想重新生成它们,您必须先删除它们。

忽略表

也可以忽略一些表格。要这样做,您可以修改配置文件中的 ignoredTables 参数

netteRepo:
    ignoreTables:
        # These are the defaultly ignored tables:
        migrations: true
        migration_log: true
        phoenix_log: true
        phinxlog: true

自定义继承

如果您想为生成的类设置不同的 extendsimplements,您可以通过在您的配置文件中添加条目来实现

netteRepo:
    inheritance:
        AuthorRepositoryBase:
            extends: 'App\Repository\PeopleRepositoryBase'
            implements: ['App\Repository\PersonRepositoryInterface']
        AuthorQuery:
            extends: 'App\Repository\PeopleQueryBase'
        Person:
            implements: ['App\Repository\PersonInterface']
  • 每个生成的类都可以用于此。
  • 键是简短类名(不包含命名空间)。生成的类设计为没有命名空间冲突,所以这不应该是一个问题。
  • extends 必须是字符串或 null/未指定。
  • implements 必须是字符串数组或空数组或 null/未指定。
  • 您不能取消实现一个接口。

此配置模式有点冗长,但一旦您看到它,就非常直观,且易于阅读。

行为和特性

DateBehavior

此行为自动将 created_atupdated_at 列设置为插入或更新行时的当前日期和时间。

FilterBehavior

此行为将默认的 where() 条件应用到每个 select 查询中。

KeepDefaultBehavior

此行为确保在默认列中始终至少有一行具有真值。这对于标志列很有用。

SoftDeleteBehavior

此行为通过将 deleted_at 列设置为当前日期和时间来标记行已删除,而不是从表中删除它。

LastManStandingBehavior

此行为防止删除匹配给定查询的表中最后一行。

TreeTraverseBehavior

此行为管理表示表层次结构的 lftrgt 列。它在插入或更新行时自动更新它们。

SortingTrait

此特性为存储库添加了更改行顺序的方法,例如 moveUp()、moveDown()、insertBefore() 和 insertAfter()。

CastBehavior

此行为在从数据库检索值时自动将值转换为指定的类型。

有一些预定义的转换,但您也可以定义自己的。

  • JsonCastBehavior:将值从 JSON 转换为 PHP 数组,反之亦然。
  • CarbonCastBehavior:将值从 MySQL datetime 转换为 CarbonImmutable,反之亦然。

事件

在您的存储库中可以监听几个事件:Insert、Update、Delete、Select、Load

要实现您自己的事件订阅者,创建一个新的类,它扩展了 Efabrica\NetteRepository\Subscriber\EventSubscriber 并在容器中注册它。由于它扩展了 EventSubscriber,它将自动被检测到。