loophp/repository-monadic-helper

单子 Doctrine 仓库辅助类和服务。

1.1.0 2023-03-21 08:18 UTC

This package is auto-updated.

Last update: 2024-09-12 15:08:02 UTC


README

Latest Stable Version GitHub stars Total Downloads GitHub Workflow Status Type Coverage License Donate!

Doctrine 仓库单子辅助器

描述

此项目提供了必要的类和服务,使用单子来更函数式地使用 Doctrine 仓库。

此项目还展示了以更简洁的方式与仓库和非确定性数据存储(在这种情况下是数据库)交互的方法。

无需始终检查实体是否存在,因此我们可以减少条件和冗余,而专注于重要且相关的部分。

当使用正确类型化的单子和回调时,类型不一致将被静态分析工具立即检测到。这为设计和数据转换方法提供了一种更安全、更好的方式。

在此项目中使用的单子是来自Lamphpda的 contrib 包提供的 Either 单子,由Marco Perone提供。

历史

此项目最初是为了满足我的个人需求,以及许多其他原因而开始的。

我首先想要消除我代码中所有必要的检查和条件,以测试实体是否存在。

然后,我发现这在我的朋友、同事和问题队列中是一个常见的模式。

消除条件的实用方法是采用更声明性的编程方式,并采用更函数式的编程风格。

最后,我愿意更多地了解单子,这是一种函数式编程的“设计模式”,我开始编写这个包。

这个包没有野心成为使用 Doctrine 仓库的de-facto标准,但它可能有助于人们理解单子是什么,如何使用它们,并希望减少他们代码中的条件数量。

在此处使用的单子包是一个任意选择。我可以用其他包,但marcosh/lamphpda似乎是最好的选择,尤其是在您使用静态分析工具分析代码以提前检测问题,而不是运行它们的单元测试时。

安装

composer require loophp/repository-monadic-helper

使用

要使用此包并拥有单子仓库,有以下3种选项:

  • 不修改现有的 Doctrine 仓库
    • 选项 1:通过使用一个创建单子仓库的服务,从实体类名或现有仓库中创建。
  • 修改现有的 Doctrine 仓库
    • 选项 2:通过添加一个接口、一个特质和相关的类型信息,如:@implements MonadicServiceEntityRepositoryInterface<EntityClassName>
    • 选项 3:将extends ServiceEntityRepository替换为extends MonadicServiceEntityRepository,并添加相关的类型信息,如:@extends MonadicServiceEntityRepository<EntityClassName>

在我看来,使用此包的最佳方式是使用第一种选项

必须将EntityClassName替换为仓库中使用的实体类,以便静态分析工具正确推断类型。

对于选项 2 和 3,以下是一个常用的 Doctrine 仓库示例

<?php

declare(strict_types=1);

namespace App\Repository;

use App\Entity\MyCustomEntity;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

/**
 * @method MyCustomEntity|null find($id, $lockMode = null, $lockVersion = null)
 * @method MyCustomEntity|null findOneBy(array $criteria, array $orderBy = null)
 * @method MyCustomEntity[]    findAll()
 * @method MyCustomEntity[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class MyCustomEntityRepository extends ServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, MyCustomEntity::class);
    }
}

选项 1:使用服务

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Entity\MyCustomEntity;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use loophp\RepositoryMonadicHelper\MonadicRepositoryFactoryInterface;
use Throwable;

final class MyCustomController
{
    public function __invoke(
        MonadicRepositoryFactoryInterface $monadicRepositoryFactory
    ) {
        $body = $monadicRepositoryFactory
            ->fromEntity(MyCustomEntity::class)
            ->eitherFind(123) // This returns a Either monad.
            ->map(
                static fn (MyCustomEntity $entity): string => $entity->getTitle()
            )
            ->eval(
                static fn (Throwable $exception): string => $exception->getMessage(),
                static fn (string $entity): string => $entity
            );

        return new Response($body);
    }
}

选项 2:在现有仓库中使用接口和特质

升级您的Doctrine存储库到

<?php

declare(strict_types=1);

namespace App\Repository;

use App\Entity\MyCustomEntity;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use loophp\RepositoryMonadicHelper\Doctrine\MonadicServiceEntityRepositoryInterface;
use loophp\RepositoryMonadicHelper\MonadicServiceEntityRepositoryTrait;
use Throwable;

/**
 * @implements MonadicServiceEntityRepositoryInterface<Throwable, MyCustomEntity>
 */
class MyCustomEntityRepository extends ServiceEntityRepository implements MonadicServiceEntityRepositoryInterface
{
    Use MonadicServiceEntityRepositoryTrait;

    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, MyCustomEntity::class);
    }
}

选项 3:用 MonadicServiceEntityRepository 替换 ServiceEntityRepository

升级您的Doctrine存储库到

<?php

declare(strict_types=1);

namespace App\Repository;

use App\Entity\MyCustomEntity;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use loophp\RepositoryMonadicHelper\MonadicServiceEntityRepository;

/**
 * @extends MonadicServiceEntityRepository<MyCustomEntity>
 */
class MyCustomEntityRepository extends MonadicServiceEntityRepository
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, MyCustomEntity::class);
    }
}

API

使用服务(选项 1)或升级您的存储库(选项 2 和 3)时,以下方法将可用。

对于每个API方法,占位符 MyCustomEntity 应替换为您所引用的实体。

eitherFind

签名

eitherFind(int|string $id): Either<Throwable, MyCustomEntity>

当返回结果为 null 时,将生成异常并在monad中包装。

eitherFindAll

签名

eitherFindAll(): Either<Throwable, list<MyCustomEntity>>

当返回结果为空时,将生成异常并在monad中包装。

eitherFindBy

签名

eitherFindBy(array $criteria, ?array $orderBy = null, ?int $limit = null, ?int $offset = null): Either<Throwable, list<MyCustomEntity>>

当返回结果为空时,将生成异常并在monad中包装。

eitherFindOneBy

签名

eitherFindOneBy(array $criteria): Either<Throwable, MyCustomEntity>

当返回结果为 null 时,将生成异常并在monad中包装。

待办事项

  • 改进文档和代码示例。

贡献

请随意通过发送拉取请求来贡献。我们通常是一个非常响应的团队,我们将从开始到结束帮助您通过您的拉取请求。

由于某些原因,如果您不能为代码做出贡献,但愿意帮忙,赞助是一个好、合理和安全的方式,以表达对我们投入在这个包上的时间的感激之情。

Github 上赞助我,或赞助 任何贡献者

变更日志

请参阅 CHANGELOG.md,以查看基于 git 提交 的变更日志。

有关更详细的变更日志,请检查 发布变更日志