20

我有一个关于 Symfony 2 最佳实践的问题。抱歉,如果它有点模糊和主观。我想我可以将我的问题总结为:

“存储库总是适合查询的地方吗?”。

现在,我将大部分学说查询放在实体存储库中。我的大多数控制器操作都会执行典型的操作,例如查询实体或实体集合,根据结果抛出异常或重定向,否则更新一个或多个实体。大多数操作比标准的 ->find、->findBy 等查询更复杂。大多数需要连接。当查询涉及多个实体时,有时我不确定它应该进入哪个存储库。我猜有查询的根实体,但是......有时来自连接实体的数据更重要和相关,所以感觉不对将其放入根实体的存储库中。

这没问题,但我倾向于在我的存储库中得到许多几乎相同但略有不同的查询。想出名字并准确跟踪每个人的工作可能会让人感到困惑和乏味。这些查询中的大多数仅由同一控制器中的一两个(通常很少使用)控制器操作使用。我觉得我的存储库里有太多专门的、很少使用的东西。

似乎除了最简单的动作之外的所有动作都应该封装在一个对象或服务中。因此,我已经开始直接在服务而不是存储库中进行大量查询。很容易在一个地方查看所有动作。这是一个好的做法吗?

4

3 回答 3

12

您的查询应该保存在您的实体存储库中,而不是您的控制器中,以便能够轻松地重用它们。

这就是存储库的实际用途。为数据库查询提供可重用的位置。

然而,在某些情况下,可以改进将所有查询保存在存储库中……尤其是在过滤可能需要快速大量查询的情况下。

Benjamin Eberlei(Doctrine 的创建者)认为一个类中有 5 个公共方法是可以的,而 10 个相当大。他最近在他的博客上发表了一篇有趣的文章,名为“ On Taming Repository Classes in Doctrine ”。

我部分地喜欢 KnpLabs 在他们的DoctrineBehaviors中的可过滤存储库特征解决方案。

特性使测试更难,但您可以拥有一个更清洁、更易于维护的存储库……您应该将查询保存在其中

于 2013-06-13T15:11:20.937 回答
9

你可以做一些介于两者之间的事情。

定义服务:

blog.post_manager:
    class: Acme\BlogBundle\Entity\Manager\PostManager
    arguments:
        em: "@doctrine.orm.entity_manager"
        class: Acme\BlogBundle\Entity\Post

然后创建 Manager 类:

use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityRepository;

class PostManager
{
    protected $em;

    protected $repo;

    protected $class;

    public function __construct(EntityManager $em, $class) {
        $this->em = $em;
        $this->class = $class;
        $this->repo = $em->getRepository($class);
    }

    public function get($id)
    {
        return $this->repo->findById($id);
    }
}

这样,您仍然可以将查询留在它们所属的存储库中,同时允许通过管理器服务重用代码,该管理器服务可以在任何控制器中像这样使用:

$this->container->get('blog.post_manager')->get(1);

由于该服务负责将类和实体管理器注入到 Manager 类中,这也使控制器更薄,并更好地将其从模型中抽象出来。

于 2013-06-13T15:12:45.783 回答
0

存储库的职责是为您的域对象提供非技术接口。
实际的查询实现可以保留在存储库中,但最好将其封装在一个只负责构建查询的类中。
控制器(或应用程序服务)不应该包含查询的实现细节,它们应该只调用存储库(好像它是一个集合)或查询工厂(如果您想自己处理查询或结果)。
理想情况下,控制器应该只有很少的代码,只需调用“SRPecialized”服务。



您可以尝试实现这个简单的架构选项。它适合 Doctrine 或任何其他 ORM 库。它尊重关注点分离,正如Martin Fowler 的 PoEAA 书中所预期的那样,为持久性细节和组合的抽象提供了一个层。


为什么是存储库?

存储库的职责是处理一个集合,所以实际的操作代码应该去那里。查询应该在其他地方收集/组合/生成,例如在工厂类中(如前所述)。这允许在不触及存储库中保存的域/应用程序逻辑部分的情况下切换底层持久层实现。

您可以从中派生,具体取决于您的应用程序,但是当查询开始是动态的、带有多个参数或只是太长时,我倾向于将每个查询抽象到其自己的类中,然后从存储库中调用它们。

查询构造

每个查询都可以有一个由存储库调用的静态构造函数。对于更复杂的情况,我QueryFactory为可用的自定义查询定义了一个简单的入口点;这将允许更灵活的依赖配置,这要归功于 Symfony DI。


实现示例

namespace App\Repository;

// use statements

class ArticleRepository extends Doctrine\ORM\EntityRepository
{
    public function getPublished()
    {
        $query = App\Query\Article::getPublished(\DateTimeImmutable $after = null);
        $query_iterator = new Doctrine\ORM\Tools\Pagination\Paginator($query);
        
        // ... run query ...
    }
}

因此您可以为查询的特定变体使用自定义方法(例如 getPublished、getUnpublished、getNewArticles 或其他)。

namespace App\Query;

// use statements

final class Articles
{
    public static function getPublished(\DateTimeImmutable $after = null)
    {
        return new self(Article::PUBLISHED_STATE, $this->sqlFormat($after));
    }

    public function __constructor(string $status, \DateTimeImmutable $date_filter = null) {
        
        // ... you own complex query logic ...
    }
}

// ...

$published_query = Articles::getPublished();

// ...
  • 由于通过静态构造函数进行代理,并且在回溯堆栈上的错误时还有一个文件要经过,因此维护变得有点烦人。虽然它的扩展性更好。
  • 一般来说,它在项目中提供了一个良好且灵活的组织,可以产生许多复杂或非标准的查询。
  • 在我看来,存储库模式应该与持久性级别的实现细节无关(幕后使用什么数据库适配器:SQL?NoSQL?)。
  • 对来自不同存储库的查询常见的部分的组合和抽象更容易实现。
于 2021-11-29T22:38:31.633 回答