覆盖doctrine延迟加载方法以将计算数据包含到对象

时间:2016-04-13 19:16:15

标签: php symfony doctrine-orm

让我说我有这样的事情:

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

某些$ statistic的属性存储在数据库中,但其他一些属性是通过分析用户活动(查询数据记录)来计算的。

问题是我有一个$ user,当我运行$ user-> getStatistic()时,我得到了存储的$ statistic数据,我需要使用sql查询添加更多数据,我不知道在哪里编程此功能。 ¿覆盖存储库?我尝试重写find()方法,但它不起作用

我知道如果我使用活动记录模式,这可以毫无问题地完成,因为我可以在构造方法中访问数据库,或者可以使用getter等。

但我不知道如何用学说标准行为来做到这一点。

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

我正在使用symfony ...也许是服务或其他东西......

3 个答案:

答案 0 :(得分:4)

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

Doctrine listener

这可能是最简单的一个。使用Doctrine postLoad事件填写User模型所需的数据。

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

  1. 每次doctrine从数据库中提取User实体实例时都会运行此命令。如果它获取100个用户的列表,它将运行100次。如果您在那里执行一些耗时的任务,这可能会导致性能问题。
  2. 它们很难调试:如果遇到错误,事件通常会使代码流不那么清晰,从而使调试更加困难。如果你做的很简单,不要过度使用它们,那么它可能就好了,否则想想其他选择。
  3. 摘要学说

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

    即使我是其中一个尝试拥有最少层数和间接需求的人,我确实认为将数据持久性包装到您自己的服务中是一个好主意。它将您系统的其他部分与必须知道 数据的存储隔离开来。

    我建议不直接使用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)->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
        }
    }
    

    这种方法有许多优点:

    与Doctrine

    脱钩

    您的业务代码不再与Doctrine相关联,它根本不知道Doctrine存储库/实体管理器是否存在。如果需要,您可以更改UserRepository实施以从远程API加载用户,从磁盘上的文件....从任何地方加载。

    模型操作

    它允许您在模型进入业务逻辑之前对其进行操作,从而允许您计算不作为数据库中的字段持久存储的值。例如,从Redis中获取它们,从某些API或其他...

    清洁API

    它使您的系统具有哪些能力,使理解更容易并且更容易进行测试。

    性能优化

    作为第一种方法,它不会遇到性能问题。请看以下示例:

    您的$eventsCount型号上有User字段。

    如果您加载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 events WHERE user_id IN (:user_ids) GROUP BY user_id;
    

答案 1 :(得分:1)

您可以实现自己的存储库以包含您自己的SQL查询,Symfony在其文档中已经很好地记录了它,请参阅here

以下是我之前使用注释做过的事情(这也可以通过yaml完成,只需查看上面的链接)...

实体:

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
 */
class User
{
    // ...

    private $statistic;

    // ...
}

用户存储库:

namespace AppBundle\Entity;

use Doctrine\ORM\EntityRepository;

class ProductRepository extends EntityRepository
{
    public function getUserStats()
    {
        // Use query builder to build your query for stats here
    }
}

由于您的自定义存储库正在扩展EntityRepository,您仍然可以访问Doctrines延迟加载方法(find,findBy,findAll等...)

答案 2 :(得分:1)

也许您不需要完全用户实例。 您可以在DQL中使用NEW()语法。

class UserStatDTO
{
     private $user;
     private $statistic;
     private $sum;

     public function __construct(User $user, $statistic, $sum)
     {
         $this->user = $user;
         $this->statistic = $statistic;
         $this->sum = $sum;
     }

     public function getUser()
     {
         return $this->user;
     }

     public function getSum()
     {
         return $this->sum;
     }

     public function getStatistic()
     {
         return $this->statistic;
     }
}

class UserRepository
{
     public function getUsersWithCalculatedStat()
     { 
           return $this->getEntityManager()->createQuery('
                     SELECT NEW UserStatDTO(
                         u, u.statistic, u.count1 + u.count2
                     ) FROM User 
                  ')->getResult();
     }
}

http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html#new-operator-syntax