loophp / repository-monadic-helper
单子 Doctrine 仓库辅助类和服务。
Requires
- php: >= 8.1
- marcosh/lamphpda: ^1
Requires (Dev)
- doctrine/doctrine-bundle: ^2.6
- doctrine/orm: ^2.12
- drupol/php-conventions: ^5
- phpunit/phpunit: ^10
README
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>
- 选项 2:通过添加一个接口、一个特质和相关的类型信息,如:
在我看来,使用此包的最佳方式是使用第一种选项。
必须将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中包装。
待办事项
- 改进文档和代码示例。
贡献
请随意通过发送拉取请求来贡献。我们通常是一个非常响应的团队,我们将从开始到结束帮助您通过您的拉取请求。
由于某些原因,如果您不能为代码做出贡献,但愿意帮忙,赞助是一个好、合理和安全的方式,以表达对我们投入在这个包上的时间的感激之情。
变更日志
请参阅 CHANGELOG.md,以查看基于 git 提交 的变更日志。
有关更详细的变更日志,请检查 发布变更日志。