ZF2 + Doctrine 2 - 使用工厂创建Doctrine SQLFilter - 如何?

时间:2017-10-16 10:02:34

标签: doctrine-orm zend-framework2 doctrine-orm-filters

我明白了,答案在下面 - 留下问题和所有流程的东西,以防万一它可能会帮助某人在将来找出相同的东西,尽管大部分都是由多余的答案。

我要做的是,根据与用户相关联的公司过滤数据(User#company)。

我有几个实体用于此场景:

  • 用户
  • 公司
  • 地址

场景是数据(在本例中为Address实体对象)由User实体创建。每个User都有一个Company个实体。因此,每个Address都有Address#createdByCompany属性。

现在我正在尝试创建SQLFilter扩展程序,如Doctrine docs - "Working with Filters"所述。

我创建了以下类:

class CreatedByCompanyFilter extends SQLFilter
{
    /**
     * @var Company
     */
    protected $company;

    /**
     * @param ClassMetadata $targetEntity
     * @param string $targetTableAlias
     * @return string
     * @throws TraitNotImplementException
     */
    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
    {
        // Check if Entity implements CreatedByCompanyAwareInterface, if not, return empty string
        if (!$targetEntity->getReflectionClass()->implementsInterface(CreatedByCompanyInterface::class)) {

            return '';
        }

        if (!array_key_exists(CreatedByCompanyTrait::class, $targetEntity->getReflectionClass()->getTraits())) {

            throw new TraitNotImplementException(
                ($targetEntity->getReflectionClass()->getName()) . ' requires "' . CreatedByCompanyTrait::class . '" to be implemented.'
            );
        }

        return $targetTableAlias . '.created_by_company = ' . $this->getCompany()->getId();
    }

    /**
     * @return Company
     */
    public function getCompany(): Company // Using PHP 7.1 -> explicit return types
    {
        return $this->company;
    }

    /**
     * @param Company $company
     * @return CreatedByCompanyFilter
     */
    public function setCompany(Company $company): CreatedByCompanyFilter
    {
        $this->company = $company;
        return $this;
    }
}

要使用此过滤器,它将在配置和设置中注册,以加载到模块Bootstrap(onBootstrap)中。到目前为止一切顺利,以上内容得到了应用。

但是,上述内容未通过工厂模式使用。另外,您可能会注意到addFilterConstraint(...)使用$this->getCompany()->getId(),但$this->setCompany()未在任何地方调用,而是在null值上创建函数调用。

如何使用工厂创建此类,使用ZF2 ServiceManager的常规路径或通过注册Doctrine本身?

我已尝试过的内容

在过去的几个小时里,谷歌在寻找解决方案的过程中,我也试过以下

1 - ZF2工厂

使用在配置中注册Factory的默认ZF2方法不起作用:

'service_manager' => [
    'factories' => [
        CreatedByCompanyFilter::class => CreatedByCompanyFilterFactory::class,
    ],
],

工厂根本就没有被召唤。这可能与执行顺序有关。我正在考虑在ServiceManager完全启动并运行之前设置Doctrine SQLFilters,以防万一我正在尝试做的情况:根据一些基于角色的东西(或“公司内容”)为用户过滤数据这种情况)。

2 - Ocramius's ZF2 Delegator Factories

在研究这个问题时,我找到了Ocramius的代表工厂。非常有趣的东西,绝对值得一读,效果很好。但是,不适合我的情况。我跟着他的导游创建了一个CreatedByCompanyFilterDelegatorFactory。这是我在配置中注册的,但没有结果,实际的工厂永远不会被调用。

(抱歉,已删除代码)

工厂我正在尝试投放 更新为*ListenerFactory,请参阅下面的“正在尝试”

class CreatedByCompanyListenerFactory implements FactoryInterface
{
    // NOTE: REMOVED DOCBLOCKS/COMMENTS FOR LESS CODE
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $authService = $serviceLocator->get('zfcuser_auth_service');
        $user = $authService->getIdentity();

        $listener = new CreatedByCompanyListener();
        $listener->setCompany($user->getCompany());

        return $listener;
    }
}

目前正在尝试

我想我可以尝试从Gedmo Extensions和Doctrine Events手册中取出一个页面,并使用将监听器挂钩到loadClassMetadata事件的格式。

例如,Gedmo的SoftDeleteable在Doctrine的配置中具有以下配置以使其工作:

'eventmanager' => [
    'orm_default' => [
        'subscribers' => [
            SoftDeleteableListener::class,
        ],
    ],
],
'configuration' => [
    'orm_default' => [
        'filters' => [
            'soft-deleteable' => SoftDeleteableFilter::class,
        ],
    ],
],

所以我想,好吧,让我们尝试一下,并设置以下内容:

'eventmanager' => [
    'orm_default' => [
        'subscribers' => [
            CreatedByCompanyListener::class,
        ],
    ],
],
'configuration' => [
    'orm_default' => [
        'filters' => [
            'created-by-company' => CreatedByCompanyFilter::class,
        ],
    ],
],
'service_manager' => [
    'factories' => [
        CreatedByCompanyListener::class => CreatedByCompanyListenerFactory::class,
    ],
],

目的是使用*ListenerFactory将经过身份验证的User实体纳入*Listener*Listener反过来会将与Company相关联的User传递到传递给EventArgs的{​​{1}}。从理论上讲,这应该是可用的。

目前,CreatedByCompanyFilter是以下内容:

CreatedByCompanyLister

但是,我对class CreatedByCompanyListener implements EventSubscriber { //NOTE: REMOVED DOCBLOCKS/HINTS FOR LESS CODE protected $company; public function getSubscribedEvents() { return [ Events::loadClassMetadata, ]; } public function loadClassMetadata(EventArgs $eventArgs) { $test = $eventArgs; // Debug line, not sure on what to do if this works yet ;) } // $company getter/setter } 中使用的Zend\Authentication\AuthenticationService感到困惑,在尝试使用*ListenerFactory获取User的身份时会引发异常。

在内部,此功能继续进入$user = $authService->getIdentity();类,第70行,(ZfcUser\Authentication\Storage\Db),崩溃,投掷$identity = $this->getMapper()->findById($identity);

  

getMapper()

踩过(PhpStorm中的xdebug),以某种方式调用它让它继续一点(wtf ...的位),但随后抛出:

  

Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for doctrine.entitymanager.orm_default

“* Exception * previous”表示:

  

An exception was raised while creating "zfcuser_user_mapper"; no instance returned

我独立地知道所有这些错误意味着什么,但我从来没有看到过所有这些错误都被同一个函数调用(Circular dependency for LazyServiceLoader was found for instance Doctrine\ORM\EntityManager)抛出。有任何想法吗?

1 个答案:

答案 0 :(得分:0)

当然,我当时认为太复杂了。我现在使用以下类运行它:

  • CreatedByCompanyFilter
  • CreatedByCompanyListener
  • CreatedByCompanyListenerFactory

我需要的唯一配置如下:

'listeners' => [
    CreatedByCompanyListener::class,
],
'service_manager' => [
    'factories' => [
        CreatedByCompanyListener::class => CreatedByCompanyListenerFactory::class,
    ],
],

注意:未在Doctrine配置中注册过滤器。所以如果你想做同样的话,请不要在下面做!

'doctrine' => [
    'eventmanager' => [
        'orm_default' => [
            'subscribers' => [
                CreatedByCompanyListener::class,
            ],
        ],
    ],
    'configuration' => [
        'orm_default' => [
            'filters' => [
                'created-by-company' => CreatedByCompanyFilter::class,
            ],
        ],
    ],
],

重复:在这种情况下不需要上述内容 - 虽然看到我一直在做什么,但我可以看到我(以及其他人)可能会认为它可能是什么。

因此,只有3个类,其中一个是在Listener配置中注册ListenerFactory的注册ServiceManager

逻辑最终是Listener需要使用Filter启用Doctrine EntityManager,之后可以在返回值中设置所需的参数(过滤器) )。不确定“Daredevel”是否有Stackoverflow帐户,但thanks for this article!我注意到启用了过滤器,然后设置了它的参数。

因此,Listener启用Filter并设置其参数。

CreatedByCompanyListener如下:

class CreatedByCompanyListener implements ListenerAggregateInterface
{
    // NOTE: Removed code comments & docblocks for less code. Added inline for clarity.

    protected $company;         // Type Company entity
    protected $filter;          // Type CreatedByCompanyFilter
    protected $entityManager;   // Type EntityManager|ObjectManager
    protected $listeners = [];  // Type array

    public function attach(EventManagerInterface $events)
    {
        $this->listeners[] = $events->attach(MvcEvent::EVENT_BOOTSTRAP, [$this, 'onBootstrap'], -5000);
    }

    public function detach(EventManagerInterface $events)
    {
        foreach ($this->listeners as $index => $listener) {
            if ($events->detach($listener)) {
                unset($this->listeners[$index]);
            }
        }
    }

    public function onBootstrap(MvcEvent $event)
    {
        $this->getEntityManager()->getConfiguration()->addFilter('created-by-company', get_class($this->getFilter()));
        $filter = $this->getEntityManager()->getFilters()->enable('created-by-company');

        $filter->setParameter('company', $this->getCompany()->getId());
    }

    // Getters & Setters for $company, $filter, $entityManager
}

CreatedByCompanyListenerFactory如下:

class CreatedByCompanyListenerFactory实现FactoryInterface {     public function createService(ServiceLocatorInterface $ serviceLocator)     {         $ authService = $ serviceLocator-> get('zfcuser_auth_service');         $ entityManager = $ serviceLocator-> get(EntityManager :: class);         $ listener = new CreatedByCompanyListener();

    $user = $authService->getIdentity();
    if ($user instanceof User) {
        $company = $user->getCompany();
    } else {
        // Check that the database has been created (more than 0 tables)
        if (count($entityManager->getConnection()->getSchemaManager()->listTables()) > 0) {
            $companyRepo = $entityManager->getRepository(Company::class);
            $company = $companyRepo->findOneBy(['name' => 'Guest']);
        } else {
            // Set temporary company for guest user
            $company = $this->tmpGuestCompany(); // Creates "new" Company, sets name, excludes in $entityManager from persisting/flushing it
        }
    }

    $listener->setCompany($company);
    $listener->setFilter(new CreatedByCompanyFilter($entityManager));
    $listener->setEntityManager($entityManager);

    return $listener;
}

}

最后,CreatedByCompanyFilter需要实际执行某些操作,如下所示:

class CreatedByCompanyFilter extends SQLFilter
{
    public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
    {
        // Check if Entity implements CreatedByCompanyAwareInterface, if not, return empty string
        if (!$targetEntity->getReflectionClass()->implementsInterface(CreatedByCompanyInterface::class)) {

            return '';
        }

        if (!array_key_exists(CreatedByCompanyTrait::class, $targetEntity->getReflectionClass()->getTraits())) {

            throw new TraitNotImplementedException(
                $targetEntity->getReflectionClass()->getName() . ' requires "' . CreatedByCompanyTrait::class . '" to be implemented.'
            );
        }

        $column = 'created_by_company';
        return "{$targetTableAlias}.{$column} = {$this->getParameter('company')}";
    }
}

为什么会这样做

以上代码中最重要的一点是基于之前链接的Daredevel教程:

public function onBootstrap(MvcEvent $event)
{
    $this->getEntityManager()->getConfiguration()->addFilter('created-by-company', get_class($this->getFilter()));
    $filter = $this->getEntityManager()->getFilters()->enable('created-by-company');

    $filter->setParameter('company', $this->getCompany()->getId());
}

这是我们Filter添加到第一行EntityManager的配置的地方。这允许我们使用它。必需的是,您为Filter提供一个名称,并告诉EntityManager哪个类属于第二个参数的名称。

接下来,我们按名称enable() Filter。 Daredevel的教程向我展示了enable()函数实际上返回刚启用的过滤器。

使用Filter变量中返回的$filter,我们现在可以使用该实例来设置参数,我们在上一个语句中执行这些参数。

这与我在问题中的尝试有何不同?嗯,在我的问题中,我试图以相反的方式做到这一点。我尝试通过配置和Filter启用Listener。但是,使用后一种方法,我尝试在变量中创建和存储Filter实例,设置所需的参数,然后在EntityManager中启用它,这正是{{1}的错误方式创建一个新实例(因此没有变量)。

因此,对于那些可能偶然发现同样问题的人,我会把它留在这里。