且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

覆盖原则延迟加载方法将计算数据包含到对象中

更新时间:2023-09-19 23:20:04

有很多方法可以解决您的问题。



教义听众



这可能是最简单的一个。使用原则 postLoad 事件,以填写用户模型所需的数据。



这是一个完全有效的方法,但有一些缺点:


  1. 每次doctrine从一个用户实体实例数据库。如果它获取100个用户的列表,它将运行100次。这可能会导致性能问题,如果您在那里执行一些耗时的任务。

  2. 它们很难调试:如果遇到错误,事件通常会使代码流不那么清楚,因此使调试更加困难。如果你做简单的事情,不要过度使用它们,那么可能很好,否则考虑其他选项。





我非常赞成这种方法,我几乎在每个项目中都使用它。



尽管我是尝试拥有最少层次和间接需求的人之一,但我认为将数据持久性包装到您自己的服务中是一个好主意。它隔离系统的其余部分不必知道如何存储数据。



我建议不要使用Doctrine存储库/实体管理器直。相反,将它们包装在您自己的服务中。



这使您的持久性API吱吱声干净而显而易见,同时让您能够在达到您的模式之前操纵模型商业逻辑。



以下是我如何处理您的问题的示例:

 #src / AppBundle / Repository / UserRepository.php 

class UserRepository
{
private $ em;

public function __construct(EntityManagerInterface $ em)
{
$ this-> em = $ em;
}

public function findById($ userId)
{
$ user = $ this-> em-> getRepository(User :: class) - &gt ;发现($用户id);
$ this-> calculateUserStatistics($ user);

return $ user;
}

public function save(User $ user)
{
$ this-> em-> persist($ user);
$ this-> em-> flush();
}

// ...

私有函数calculateUserStatistics(User $ user)
{
//计算并设置统计信息用户对象
}
}

这种方法有很多优点: / p>

从Doctrine退学



您的商业代码已不再适用于Doctrine,它不知道Doctrine存储库/实体管理器存在。如果需要,您可以从任何地方更改 UserRepository 实现来从远程API加载用户,从磁盘上的文件....



模型操作



它允许您在获得业务逻辑之前操纵模型,允许您计算不保留作为数据库中字段的值。例如,从Redis,某些API或其他...获取它们。



清洁API



它使真的很明显你的系统有哪些能力,使得理解更容易,并且允许更容易的测试。



性能优化



它不以性能问题为首,以下示例:



您的用户$中有 $ eventsCount 字段c $ c> model。



如果您加载100个用户的列表并使用第一种方法,则需要启动100个查询来计算属于每个用户的事件数。

  SELECT COUNT(*)FROM events WHERE user_id = 1; 
SELECT COUNT(*)FROM events WHERE user_id = 2;
SELECT COUNT(*)FROM events WHERE user_id = 3;
SELECT COUNT(*)FROM events WHERE user_id = 4;
...

然而,如果你有自己的UserRepository实现,你可以做方法 getEventCountsForUsers($ userIds)将触发一个查询:

  SELECT COUNT(*)FORM事件WHERE user_id IN(:user_ids)GROUP BY user_id; 


let say that I have something like this:

class User{
    /**
     * Object that is lazy loaded 
     */
    private $statistic; //object with some stored data and some calculated data
}

Some of the $statistic's properties are stored in the DB but some other of them are calculated by analyzing the user activity (querying data records).

the thing is that I got a $user and when I run $user->getStatistic() as spected, I get the stored $statistic data and I need to add more data using sql queries and I don't know where to program this functionality. ¿overriding the Repository? I try overriding the find() method but it doesn't work

I know that if I use the active record pattern this can be done with no problem giving that I can access the DB in the construct method or the getters maybe, etc.

but I don't know how this could be done with doctrine standard behavior.

I believe that there must be a way to ensure that every instance of the Statistic Class have this calculated data on it.

I'm using symfony... maybe a service or something...

There are a number of ways to solve your problem.

Doctrine listener

This is probably the easiest one. Use Doctrine postLoad event to fill out data you need on your User model.

This is a completely valid approach, but has a couple of drawbacks:

  1. This will be ran every time doctrine fetches an User entity instance from database. If it fetches a list of 100 users, it will be ran 100 times. This could make a performance problem if you do some time-consuming tasks in there.
  2. They are hard to debug: if an error is encountered, events usually make code flow a lot less clear and therefore make debugging harder. If you do simple stuff, and don't overuse them, then it's probably fine, otherwise think about other options.

Abstracting away doctrine

I'm strongly in favor of this approach and I use it in almost every project.

Even though I'm one of the people who try to have the least amount of layers and indirection necessary, I do think that wrapping data persistence into your own services is a good idea. It isolates rest of your system from having to know how your data is stored.

I suggest not using Doctrine repositories/Entity manager directly. Instead, wrap them in your own services.

This makes your persistence API squeaky clean and obvious, while giving you ability to manipulate your models before they reach your business logic.

Here is an example of how I would approach your problem:

# src/AppBundle/Repository/UserRepository.php

class UserRepository
{
    private $em;

    public function __construct(EntityManagerInterface $em)
    {
        $this->em = $em;
    }

    public function findById($userId)
    {
        $user = $this->em->getRepository(User::class)->find($userId);
        $this->calculateUserStatistics($user);

        return $user;
    }

    public function save(User $user)
    {
        $this->em->persist($user);
        $this->em->flush();
    }

    // ...

    private function calculateUserStatistics(User $user)
    {
        // calculate and set statistics on user object
    }
}

This approach has a number of advantages:

Decoupling from Doctrine

Your business code is no longer coupled to Doctrine, it doesn't know that Doctrine repositories/entity manager exist at all. If need arises, you can change UserRepository implementation to load users from remote API, from file on disk....from anywhere.

Model manipulation

It allows you to manipulate your models before they get to business logic, allowing you to calculate values not persisted as a field in database. Eg, to fetch them from Redis, from some API or other...

Cleaner API

It makes it really obvious what abilities your system has, making understanding easier and allowing easier testing.

Performance optimisation

It doesn't suffer from performance issues as first approach. Take the following example:

You have $eventsCount field on your User model.

If you load list of 100 users and use first approach, you would need to fire 100 queries to count number of events belonging to each user.

SELECT COUNT(*) FROM events WHERE user_id = 1;
SELECT COUNT(*) FROM events WHERE user_id = 2;
SELECT COUNT(*) FROM events WHERE user_id = 3;
SELECT COUNT(*) FROM events WHERE user_id = 4;
...

If you have your own UserRepository implementation, however, you can just make method getEventCountsForUsers($userIds) which would fire one query:

SELECT COUNT(*) FORM events WHERE user_id IN (:user_ids) GROUP BY user_id;