如何在我的应用程序中使用越来越多的查询列表来组织和实施关注点分离和良好的 OOP?

How to organize and implement Separation of Concern and good OOP with a growing list of queries throughout my app?

在哪里放置越来越多的查询列表(主要是所有 "SELECT" 查询)以及如何在我的应用程序中正确访问它们?
我正在寻求重构我的应用程序,该应用程序目前在我的应用程序中的所有服务中都有查询。我开始遇到 DRY 问题,其中我有与我已经编写的其他查询类似的大小查询。我也有一些地方会一遍又一遍地重写这些查询的相同格式。虽然我不担心更换我选择的数据库,但我确实担心以后优化我的查询,并且想知道我是否没有更好地组织我的所有查询并将它们放在某个地方的中心位置,从而使这几乎不可能。我想遵循良好的 OOP 和 SoC 实践。

我目前在我的应用程序中有查询的示例
例如,我有一项服务可以提供某个活动中所有员工的 PDF(或内容)。该服务有一个接受一些输入(如 "sort"、"filter"、"pagination limit")的方法,并将执行具有 10 个 table 连接的查询(查询切片相当好地向下到在大多数 JOIN 处理之前的可管理行数)并以 PDF 所需的方式格式化结果。

我目前的计划
我在尝试从对象的角度完美思考我的数据库需求时遇到了麻烦。我做了很多灵活的查询,不是完全临时的,而是范围广泛的 SELECTs 有很多 JOIN 和 WHERE,但我正在尽力而为:

  1. 域对象:我将创建 类,如 usersEntityeventsEntity(每个匹配项 tables 在我的数据库中)并在其中设置属性,如 idnamephone 等。匹配数据库 table 字段。我可以创建 getters/setters 这样我就可以改变(格式化日期等内容或执行验证)。我可能会创建在我的数据库中并不真正代表单个 table 的实体,但可能代表一个公共数据集合,我发现我在我的应用程序中经常重复使用这些数据。也许称它为 usersCollectionusersEvents

  2. 数据映射器: 所以我把所有的 CUD(创建更新删除...不是读取...除了 findById)操作并将它们存储在名为 usersMapper 的 类 中, eventsMapper,等等。映射器只接受一种类型的域对象(参见上面的#1)并将处理持久性(可能还有一小组非常基本的 Reads/SELECTs,如 findById($id)findByEvent($eventId))。

现在这给我留下了越来越多的读取列表(SELECT 查询)以及将它们放在哪里?

我知道但不足的选项。可能是我误会了。

假设我有这样的查询

SELECT users.first_name, users.last_name, events.name, events.date, venues.name,
venue_types.name, GROUP_CONCAT(user_friends.last_name) 作为 'friends'
来自用户
JOIN 事件 ON(events.id = users.event_id AND events.date = :date)
加入等等....

所以我在整个 PHP 社区的博客中看到的是 3 个选择:

  1. Doctrine2 之类的调用替换我的查询调用。他们的 DQL 似乎不能使调用可重用?事物似乎以一种 return 的方式连接起来,就像 5 个实体(基于 JOIN),所有属性都为用户、事件、场所、场所类型、朋友填充。这是很多我不需要的字段,我只想要每个字段中的 1 列,正如您在上面的查询中看到的那样。另外,我不打算在这样的查询后保存任何实体。
  2. Repository 类 替换我所有的查询调用,这些调用具有如下方法:$someUserRepo-> findNameAndEventNameAndEventDateAndVenueNameAndVenueTypeNameAndFriends()。除了真正疯狂的方法名称之外,我不确定我在这里传递了什么(条件如:sortlimit、一些在哪里?)或者我return(数组?对象?)。

  3. 使用查询构建器将我所有的查询调用替换为 ORM 调用(如 Laravel 的 Eloquent)。通过将 SQL 查询替换为 please->ORM->build->this->for->me->where->andWhere->andWhere->I->dont->know->SQL,我看不出有什么收获。顺便说一下,我几乎确信 ORM 是一种反模式?

我怀疑这在整个 PHP 社区中都是一个非常复杂的问题。 或者我在这里遗漏了什么?

使用 repository 是您考虑的唯一明智的选择。

这是您可以执行的操作。

定义存储库接口

该界面将描述您如何查询某些实体。

interface Events
{
    /**
     * @return Event[]
     */
    public function findUserEvents(User $user, $sort, $limit, array $filters = []);
}

或者也许:

interface Events
{
    /**
     * @return Event[]
     */
    public function findUserEvents(User $user, Query $query);
}

class Query
{
    private $sort;
    private $limit;
    private $filters = [];

    // methods
}

实现接口

使用你喜欢的任何东西来实现接口 - Doctrine,Eloquent,普通 SQL - 任何你觉得舒服的东西。

use Doctrine\ORM\EntityRepository;

class DoctrineEvents implements Events
{
    private $entityRepository;

    public function __construct(EntityRepository $entityRepository)
    {
        $this->entityRepository = $entityRepository;
    }

    /**
     * @return Event[]
     */
    public function findUserEvents(User $user, $sort, $limit, array $filters = [])
    {
        // query $this->entityRepostory and return a collection of Event objects
    }
}

或者,您可以注入 EntityManager 而不是 EntityRepository

只是为了向您展示提供替代实现有多么容易,这里有一个 SQL 实现:

class SqlEvents implements Events
{
    private $pdo;

    public function __construct(\PDO $pdo)
    {
        $this->pdo = $pdo;
    }

    /**
     * @return Event[]
     */
    public function findUserEvents(User $user, $sort, $limit, array $filters = [])
    {
        // use $this->pdo to query the database
        // you'll need to convert results into a collection of Event objects 
    }
}

简单。对于测试,您可以提供内存中实现。

依赖接口

每当您需要闪亮的新存储库时,请始终使用接口类型提示。这将允许您在觉得开始时选择的实现不再起作用时替换实现。

class MyController
{
    private $events;
    private $view;

    public function __construct(Events $events, View $view)
    {
        $this->events = $events;
    }

    public function listAction(Request $request)
    {
        $user = // ...

        $events = $this->events->findUserEvents($user, 'ASC', 10);

        return $this->view->render('list.html', ['events' => $events]);
    }
}

在此示例中,MyController 可以与 Events 接口的任何实现一起使用。它不关心它是 Doctrine,eloquent 还是 sql.

选择实施方式

我个人的偏好是使用 Doctrine。它会处理很多事情,比如水合对象或缓存。当您需要更好的性能时,您是否需要手动编写 SQL 查询,您始终可以针对此特定查询执行此操作。并非所有事情都需要以单一方式实现,并且由于您与实现分离,因此很容易更改。