Symfony 2.0中如何在实体中获取服务

38

我正在搜索并且找不到答案。我在我的应用程序中有数据库角色模型。用户可以拥有一个角色,但这个角色必须存储到数据库中。

但是然后用户需要从数据库中添加默认角色。因此我创建了一个服务:

<?php

namespace Alef\UserBundle\Service;

use Alef\UserBundle\Entity\Role;

/**
 * Description of RoleService
 *
 * @author oracle
 */
class RoleService {

    const ENTITY_NAME = 'AlefUserBundle:Role';

    private $em;

    public function __construct(EntityManager $em)
    {
        $this->em = $em;
    }

    public function findAll()
    {
        return $this->em->getRepository(self::ENTITY_NAME)->findAll();
    }

    public function create(User $user)
    {
        // possibly validation here

        $this->em->persist($user);
        $this->em->flush($user);
    }

    public function addRole($name, $role) {
        if (($newrole = findRoleByRole($role)) != null)
            return $newrole;
        if (($newrole = findRoleByName($name)) != null)
            return $newrole;

        //there is no existing role
        $newrole = new Role();
        $newrole->setName($name);
        $newrole->setRole($role);

        $em->persist($newrole);
        $em->flush();

        return $newrole;
    }

    public function getRoleByName($name) {
        return $this->em->getRepository(self::ENTITY_NAME)->findBy(array('name' => $name));
    }

    public function getRoleByRole($role) {
        return $this->em->getRepository(self::ENTITY_NAME)->findBy(array('role' => $role));
    }

}

我的 services.yml 文件内容是:

alef.role_service:
    class: Alef\UserBundle\Service\RoleService
    arguments: [%doctrine.orm.entity_manager%]

现在我想在两个地方使用它: UserControllerUser 实体。如何在实体内获取它们? 至于控制器,我认为我只需要:

$this->get('alef.role_service');

但是如何在实体内部获取服务?

3个回答

46

不需要这样做。这是一个非常普遍的问题。实体只应该知道其他实体,而不是实体管理器或其他高级服务。向这种开发方式的过渡可能有点具有挑战性,但通常是值得的。

你要做的是在加载用户时加载角色。通常你会得到一个 UserProvider 来完成此类操作。你是否阅读过有关安全性的部分?那应该是你的起点:

http://symfony.com/doc/current/book/security.html


39

首先,Symfony框架的设计初衷并不是让服务被用于实体中,因此导致将服务注入实体非常困难。因此,最佳实践是重新设计你的应用程序,不需要在实体中使用服务。

然而,我发现有一种方法可以解决这个问题,而不需要干扰全局内核。

Doctrine实体具有生命周期事件,您可以将事件侦听器挂接到其中一个事件上,详见http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events 。为了举例说明,我将使用postLoad,它会在实体创建后不久触发。

事件监听器可以作为服务进行创建,并将其它服务注入其中。

在app/config/config.yml中添加:

services:
     example.listener:
           class: Alef\UserBundle\EventListener\ExampleListener
     arguments:
           - '@alef.role_service'
     tags:
           - { name: doctrine.event_listener, event: postLoad }

添加到您的实体:

 use Alef\UserBundle\Service\RoleService;

 private $roleService;

 public function setRoleService(RoleService $roleService) {
      $this->roleService = $roleService;
 }

并添加新的 EventListener:

namespace Alef\UserBundle\EventListener;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Alef\UserBundle\Service\RoleService;

class ExampleListener
{
     private $roleService;

     public function __construct(RoleService $roleService) {
         $this->roleService = $roleService;
     }

     public function postLoad(LifecycleEventArgs $args)
     {
         $entity = $args->getEntity();
         if(method_exists($entity, 'setRoleService')) {
             $entity->setRoleService($this->roleService);
         }
     }
}

请记住,这种解决方案仍然是一种快速而粗糙的方法,你真的应该考虑以正确的方式重新设计你的应用程序。


21
这是针对问题的最佳解决方案和最精确的答案。我讨厌那些“你做不到”、“这个设计模式非常错误”或者“傻瓜,这将毁掉一切!”等无用大师们写下的回答。“有时候,更差的也是更好的”;) 再次感谢。 - Astinus Eberhard
14
很遗憾,“错误的设计模式”、“MCV模型”、“单独的业务层”等等都会在“老板”想要立刻完成任务时被抛弃。谢谢@Kai的回答,正是我想要的。 - Zuhayer Tahir
3
关于 EventListener 的一件我不喜欢的事情是它会针对你拥有的每个实体都被调用。 - Roman Newaza
1
我相信这应该被选为答案。 - Kyeno
1
关于不喜欢这个答案的评论,因为事件监听器针对每个实体都被调用,我也要指出,你已经违反了Symfony的意图,将服务放在实体内部。真正的答案是重新设计你的应用程序,如果你想以正确的方式完成它,就不要在实体中使用服务。这只是一个快速而肮脏的解决方案,是我发现做你不应该做的事情的最佳方法。 - Kai
显示剩余3条评论

1

感谢上面Kai的回答,但它不兼容Symfony 5.x。

明确指出这是一种不好的做法,但在某些特殊情况下是必需的,比如遗留代码或糟糕的数据库设计(作为模式迁移前的临时解决方案)

就像在我的情况下,我使用这段代码与邮件程序和翻译器一起使用,这会在Symfony >= 5.3中引入一个私有属性问题,因此以下是适用于最新版本Symfony的解决方案:

在config/services.yaml中:

services:
    Alef\UserBundle\EventListener\ExampleListener:
        tags:
           - { name: doctrine.event_listener, event: postLoad }

ExampleListener:
namespace Alef\UserBundle\EventListener;

use Doctrine\ORM\Event\LifecycleEventArgs;
use Alef\UserBundle\Entity\Role;

class ExampleListener
{

    public function postLoad(LifecycleEventArgs $postLoad): void
    {
        $entity = $postLoad->getEntity();
        if ($entity instanceof User) {
            $repository = ;
            $entity->roleRepository(
                $postLoad->getEntityManager()->getRepository(Role::class)
            );
        }
    }
}

在你的实体中(或者如果你在多个实体中使用它,则在 trait 中):


    use Alef\UserBundle\Service\RoleService;

    /** @internal bridge for legacy schema */
    public function roleRepository(?RoleRepository $repository = null) {
        static $roleRepository;
        if (null !== $repository) {
            $roleRepository = $repository;
        }
        return $roleRepository;
    }

    public function getRoleByName($name) {
        return $this->roleRepository()->findBy(array('name' => $name));
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接