覆盖原则延迟加载方法将计算数据包含到对象中 [英] Override doctrine lazy loading method to include calculated data to an object

查看:107
本文介绍了覆盖原则延迟加载方法将计算数据包含到对象中的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

让我说这样的话:

  class User {
/ **
*懒惰的对象
* /
private $ statistic; //对象与一些存储的数据和一些计算数据
}

属性存储在数据库中,但其中一些则通过分析用户活动(查询数据记录)计算。



事情是我有一个$用户,我运行$ user-> getStatistic()作为观众,我得到存储的$统计数据,我需要使用sql查询添加更多的数据,我不知道在哪里编程这个功能。
¿覆盖存储库?我尝试覆盖find()方法,但它不工作



我知道如果我使用活动的记录模式,这可以完成没有问题,我可以访问构造方法中的数据库或者getter可能等等。



但是我不知道这样做可以用doctrine标准行为来实现。 b
$ b

我相信必须有办法确保统计类的每个实例都有这样的计算数据。



我'使用symfony ...可能是服务或某事...

解决方案

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



教义听众



这可能是最简单的一个。使用原则 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) - > ;发现($用户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;

这篇关于覆盖原则延迟加载方法将计算数据包含到对象中的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆