我明白了,答案在下面 - 留下问题和所有流程的东西,以防万一它可能会帮助某人在将来找出相同的东西,尽管大部分都是由多余的答案。
我要做的是,根据与用户相关联的公司过滤数据(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
)抛出。有任何想法吗?
答案 0 :(得分:0)
当然,我当时认为太复杂了。我现在使用以下类运行它:
我需要的唯一配置如下:
'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}的错误方式创建一个新实例(因此没有变量)。
因此,对于那些可能偶然发现同样问题的人,我会把它留在这里。