如何在我的应用程序中组织和实现关注点分离和良好的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(或者更确切地说是内容)。该服务有一个方法,可以接受一些输入(如"排序"、"筛选"、"分页限制"),并将执行具有10个表联接的查询(在大多数JOIN工作之前,查询会很好地划分到可管理的行数),并以PDF所需的方式格式化结果。

到目前为止我的计划
我很难从对象的角度完美地考虑我的数据库需求。我做了很多灵活的查询,不是所有的都是特别的,而是有很多JOIN和WHERE的各种SELECT,但我正在尽我所能:

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

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

现在,这给我留下了一个不断增长的Reads(SELECT查询)列表,以及将它们放在哪里?

我知道的选项,但没有达到。也许我误解了他们

假设我有一个类似的查询

SELECT users.first_name,users.last_name,events.name,events.date,venues.name,venue_types.name,GROUP_CONCT(user_friends.last_name)为"好友"FROM用户将事件加入ON(events.id=users.event_id AND events.date=:date)加入等…

因此,我在PHP社区的博客中看到的是三种选择:

  1. 将我的查询调用替换为Doctrine2调用。他们的DQL似乎不能使呼叫重复使用?事情似乎是以一种方式连接的,它会返回5个实体(基于JOIN),所有属性都为用户、活动、场地、场地类型和朋友填充。这是我不需要的很多字段,我只需要每个字段中有一列,正如你在上面的查询中看到的那样。此外,我不打算在这样的查询后保存任何实体
  2. 将我的所有查询调用替换为具有以下方法的Repository类:$someUserRepo-> findNameAndEventNameAndEventDateAndVenueNameAndVenueTypeNameAndFriends()。除了非常疯狂的方法名称之外,我不确定我在这里传递了什么(条件如:排序限制,几个哪里?)或我返回了什么(数组?对象?)。

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

我怀疑这只是PHP社区中一个非常复杂的问题或者我在这里错过了什么

使用存储库是您考虑的唯一明智的选择。

以下是你可以做的。

定义存储库接口

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

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,而不是实体库

这里有一个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接口的任何实现一起工作。它不在乎是教条、雄辩还是sql。

选择实施

我个人倾向于使用条令。它将处理很多事情,比如对对象进行水合或缓存。当您需要更好的性能时,您是否需要为某个案例手动编写SQL查询?您可以始终为该特定查询执行此操作。并不是所有的东西都需要以单一的方式来实现,而且由于您与实现解耦,因此很容易进行更改。