Symfony最佳实践。查询应该在存储库还是服务中?

时间:2013-06-13 14:56:14

标签: symfony doctrine-orm

我对Symfony 2中的最佳做法有疑问。抱歉,如果它有点模糊和主观。我想我可以总结一下我的问题:

“存储库始终是查询的正确位置吗?”。

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

这工作正常但我倾向于在我的存储库中找到大量几乎相同但略有不同的查询。提出名字并准确跟踪每个人所做的事情会让人感到困惑和乏味。大多数这些查询仅由同一控制器中的一个或两个(通常很少使用)控制器操作使用。我觉得我用太多专门的,很少使用的东西使我的存储库变得混乱。

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

3 个答案:

答案 0 :(得分:10)

您的查询应保存在您的实体存储库中,而不应保存在您的控制器中,以便能够轻松重新使用

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

但是在某些情况下,可以改进存储库中的所有查询...尤其是在需要快速进行过滤时,可能需要进行大量查询。

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

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

Traits让测试变得更加困难,但您可以更清洁,更轻松地维护存储库 ... 您应该保留查询

答案 1 :(得分: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类中,这也使控制器更薄,更好地将其从模型中抽象出来。

答案 2 :(得分:-1)

注意:阅读,解散前。

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


为什么是仓库?

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

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

查询构造

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


实现示例

QueryInterfaceBaseRepository 指的是 Doctrine 的,但可以替换为您自定义的)

namespace App\Query\Repository;

// use statements

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

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

namespace App\Query;

// use statements

final class Articles implements QueryInterface
{
    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 ...

        $sql = "SELECT * FROM Article A WHERE status = ${$status} AND creation_date > '${after}'"
        // query is purely exemplary (it wasn't clear to Nico Haase below)

        // ... more logic
    }
}
  • 维护变得有点烦人,因为通过静态构造函数进行代理并且在追踪堆栈上的错误时还有一个文件要通过。虽然它可以更好地扩展。
  • 总的来说,它在项目中提供了良好且灵活的组织,从而产生了许多复杂或非标准的查询。
  • 在我看来,存储库模式应该与持久性级别的实现细节无关(在后台使用什么数据库适配器:SQL?NoSQL?)。因此,通过上述方法,应用层中没有特定的 SQL 泄漏。通常在项目中情况并非如此,也不是什么大问题(有时直接的具体代码只是更容易遵循),尤其是在应用程序不大或不会增长的情况下。
  • 对来自不同存储库的查询通用的部分进行组合和抽象,更容易实现。