Symfony - 可以访问实体存储库的Wrapper / Helper类

时间:2015-10-14 00:41:43

标签: php symfony doctrine-orm inversion-of-control

好的,所以我将一个小型的laravel项目转换为symfony(将变得更大,并且symfony使用的捆绑架构将是理想的)

我显然被laravels外观所破坏,并且几乎开箱即用地与现有数据库雄辩地合作。

我找不到让包装器或“帮助器”类访问实体存储库的最合适方法。

首先让我举几个例子然后我将解释我的尝试。 (我愿意为一个好的答案提供一些积分,但不幸的是,项目的时间限制不能完全等待)

所以在laravel我有我所有的模型类。然后我创建了一些包装器/帮助器类,它们本质上将数据转换为更有用的东西(即多个查询和包含更多通用信息的对象)。借助外观的魔力,我可以调用每个模型并查询它们,并将依赖注入到这些“Helper”类中。让他们非常精益。在symfony中,理想的解决方案是将所有可重用的数据库逻辑放在存储库中,确定。

在symfony中,我被控制反转(IoC)所包围;这很好但是设计模式对于我完全没有想到这个场景是不够直观的。我试图在每个存储库中创建服务,如果从控制器或其他依赖注入(DI)服务调用它,它会很有效。但是在标准的php类中,看起来我的双手并没有将实体管理器传递给每个帮助器类的构造函数。 * *颤抖

第一个限制是我没有能力改变现有表的模式(这显然不会改变问题,只是不希望任何人建议改变实体)。

那么如何实现这一目标呢。

修改

所以感谢@ mojo的评论,我已经完成了我想做的事情。如果它存在,仍在寻找更好的选择。 (见下面的编辑2)

目前我有:

config.yml docterine.orm.entity_managers:

entity_managers:
     default:
         auto_mapping: true
         connection: default
     asterisk:
         connection: asterisk
         mappings:
             AsteriskDbBundle:  ~
     asteriskcdr:
         connection: asteriskcdr
         mappings:
             AsteriskCdrDbBundle:

service.yml

services:
    app.services.doctrine.entitymanager.provider:
        class: AppBundle\Services\EntityManagerProvider
        arguments: [@doctrine]
        tags:
             - {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}

EntityManagerProvider

namespace AppBundle\Services;
use Doctrine\Bundle\DoctrineBundle\Registry as DoctrineRegistry;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Config\Definition\Exception\Exception;

class EntityManagerProvider
{
    /** @var  DoctrineRegistry */
    private static $doctrine;

    public function __construct(DoctrineRegistry $doctrine)
    {
        static::$doctrine = $doctrine;
    }

    /**
     * @param $class
     * @return EntityManager
     */
    public static function getEntityManager($class)
    {
        if(($em = static::$doctrine->getManagerForClass($class)) instanceof EntityManager == false)
            throw new Exception(get_class($em) . ' is not an instance of ' . EntityManager::class);

        return $em;
    }

    // oh man does this feel dirty
    public function onKernelRequest($event)
    {
        return;
    }
}

示例控制器

$extension = Extension::createFromDevice(DeviceRepository::findById(92681));

ExtendedEntityRepository

namespace AppBundle\Entity;
use AppBundle\Services\EntityManagerProvider;
use AppBundle\Utils\DateTimeRange;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Mapping\ClassMetadata;
use Symfony\Component\Config\Definition\Exception\Exception;

class ExtendedEntityRepository extends \Doctrine\ORM\EntityRepository
{
    /** @var  ExtendedEntityRepository */
    protected static $instance;

    public function __construct(EntityManager $entityManager, ClassMetadata $class)
    {
        parent::__construct($entityManager, $class);

        if(static::$instance instanceof static == false)
            static::$instance = $this;
    }

    // some horribly dirty magic to get the entity that belongs to this repo... which requires the repos to have the same name and exist one directory down in a 'Repositories' folder
    public static function getInstance()
    {
        if(static::$instance instanceof static == false) {
            preg_match('/^(.*?)Repositories\\\([A-Za-z_]*?)Repository$/', static::class, $match);
            $class = $match[1] . $match[2];
            $em = EntityManagerProvider::getEntityManager($class);
            static::$instance = new static($em, $em->getClassMetadata($class));
        }
        return static::$instance;
    }

    public static function findById($id)
    {
        return static::getInstance()->find($id);
    }

    public static function getQueryBuilder()
    {
        return static::getInstance()->getEntityManager()->createQueryBuilder();
    }

    public static function getPreBuiltQueryBuilder()
    {
        return static::getQueryBuilder()->select('o')->from(static::getInstance()->getClassName(), 'o');
    }

    public static function findByColumn($column, $value)
    {
        //if($this->getClassMetadata()->hasField($column) == false)
        //    throw new Exception($this->getEntityName() . " does not contain a field named `{$column}`");
        return static::getPreBuiltQueryBuilder()->where("{$column} = ?1")->setParameter(1, $value)->getQuery()->execute();
    }

    public static function filterByDateTimeRange($column, DateTimeRange $dateTimeRange, QueryBuilder $queryBuilder = null)
    {
        if($queryBuilder == null)
            $queryBuilder = static::getPreBuiltQueryBuilder();
        if($dateTimeRange != null && $dateTimeRange->start instanceof \DateTime && $dateTimeRange->end instanceof \DateTime) {
            return $queryBuilder->andWhere(
            $queryBuilder->expr()->between($column, ':dateTimeFrom', ':dateTimeTo')
            )->setParameters(['dateTimeFrom' => $dateTimeRange->start, 'dateTimeTo' => $dateTimeRange->end]);
        }
        return $queryBuilder;
    }
}

DeviceRepository

namespace Asterisk\DbBundle\Entity\Repositories;
use AppBundle\Entity\ExtendedEntityRepository;

/**
 * DeviceRepository
 *
 * This class was generated by the Doctrine ORM. Add your own custom
 * repository methods below.
 */
class DeviceRepository extends ExtendedEntityRepository
{
    //empty as it only needs to extend the ExtendedEntityRepository class
}

扩展

namespace AppBundle\Wrappers;

use Asterisk\DbBundle\Entity\Device;

class Extension
{
    public $displayName;
    public $number;

    public function __construct($number, $displayName = "")
    {
        $this->number = $number;
        $this->displayName = $displayName;
    }

    public static function createFromDevice(Device $device)
    {
        return new Extension($device->getUser(), $device->getDescription());
    }
}

代理(这是静态存储库访问权限的原因示例)

namespace AppBundle\Wrappers;


use AppBundle\Utils\DateTimeRange;
use Asterisk\CdrDbBundle\Entity\Cdr;
use Asterisk\CdrDbBundle\Entity\Repositories\CdrRepository;
use Asterisk\DbBundle\Entity\Device;
use Asterisk\DbBundle\Entity\Repositories\FeatureCodeRepository;
use Asterisk\DbBundle\Entity\Repositories\QueueDetailRepository;
use Asterisk\DbBundle\Enums\QueueDetailKeyword;

class Agent
{
    public $name;

    public $extension;

    /** @var  Call[] */
    public $calls = [];

    /** @var array|Queue[] */
    public $queues = [];

    /** @var AgentStats  */
    public $stats;

    private $_extension;

    public function __construct(Device $extension, DateTimeRange $dateTimeRange = null)
    {
        $this->_extension = $extension;
        $this->extension = Extension::createFromDevice($extension);
        $this->name = $this->extension->displayName;
        $this->calls = $this->getCalls($dateTimeRange);
        $this->stats = new AgentStats($this, $dateTimeRange);
    }

    public function getCalls(DateTimeRange $dateTimeRange = null)
    {
        /** @var CdrRepository $cdrRepo */
        $cdrRepo = CdrRepository::getPreBuiltQueryBuilder();
        $query = $cdrRepo->excludeNoAnswer($cdrRepo->filterByDateTimeRange($dateTimeRange));

        $cdrs = $query->andWhere(
                $query->expr()->orX(
                    $query->expr()->eq('src', $this->extension->number),
                    $query->expr()->eq('dst', $this->extension->number)
                )
            )->andWhere(
                $query->expr()->notLike('dst', '*%')
            )
            ->getQuery()->execute();

        foreach($cdrs as $cdr) {
            $this->calls[] = new Call($cdr);
        }
        return $this->calls;
    }

    public function getBusyRange(DateTimeRange $dateTimeRange = null)
    {
        $on = FeatureCodeRepository::getDndActivate();
        $off = FeatureCodeRepository::getDndDeactivate();
        $toggle = FeatureCodeRepository::getDndToggle();

        $query = CdrRepository::filterByDateTimeRange($dateTimeRange);

        /** @var Cdr[] $dndCdrs */
        $dndCdrs = $query->where(
                    $query->expr()->in('dst', [$on, $off, $toggle])
                )
                ->where(
                    $query->expr()->eq('src', $this->extension->number)
                )->getQuery()->execute();

        $totalTimeBusy = 0;

        /** @var \DateTime $lastMarkedBusy */
        $lastMarkedBusy = null;
        foreach($dndCdrs as $cdr) {
            switch($cdr->getDst())
            {
            case $on:
                $lastMarkedBusy = $cdr->getDateTime();
                break;
            case $off:
                if($lastMarkedBusy != null)
                    $totalTimeBusy += $lastMarkedBusy->diff($cdr->getDateTime());
                $lastMarkedBusy = null;
                break;
            case $toggle:
                if($lastMarkedBusy == null) {
                    $lastMarkedBusy = $cdr->getDateTime();
                }
                else
                {
                    $totalTimeBusy += $lastMarkedBusy->diff($cdr->getDateTime());
                    $lastMarkedBusy = null;
                }
                break;
            }
        }
        return $totalTimeBusy;
    }

    public function getQueues()
    {
        $query = QueueDetailRepository::getPreBuiltQueryBuilder();
        $queues = $query->where(
                $query->expr()->eq('keyword', QueueDetailKeyword::Member)
            )->where(
                $query->expr()->like('data', 'Local/'.$this->extension->number.'%')
            )->getQuery()->execute();

        foreach($queues as $queue)
            $this->queues[] = Queue::createFromQueueConfig(QueueDetailRepository::findByColumn('extension', $queue->id), $queue);
        return $this->queues;
    }
}

编辑2:

实际上我忘记了我将每个存储库声明为服务,所以我可以在getInstance()方法中省略黑魔法伏都教。但是在内核事件上加载服务似乎是一个坏主意......

parameters:
    entity.device: Asterisk\DbBundle\Entity\Device
services:
    asterisk.repository.device:
    class: Asterisk\DbBundle\Entity\Repositories\DeviceRepository
    factory: ["@doctrine.orm.asterisk_entity_manager", getRepository]
    arguments:
        - %entity.device%
    tags:
        - {name: kernel.event_listener, event: kernel.request, method: onKernelRequest}

编辑3

Cerad gave me an answer on my other related question建议使用单个内核事件侦听器服务并将每个存储库注入依赖项。因此允许我静态访问存储库。我唯一关心的是在每个请求上加载每个存储库所需的开销。我理想的方法是延迟加载存储库,但此时我还没有意识到一种方法。 proxy-manager-bridge看起来很有希望,但凭借我的单身模式,我觉得它不会起作用。

0 个答案:

没有答案