【问题标题】:Unable to access Symfony2 container in controller extending Symfony\Bundle\FrameworkBundle\Controller\Controller无法在扩展 Symfony\Bundle\FrameworkBundle\Controller\Controller 的控制器中访问 Symfony2 容器
【发布时间】:2014-04-23 00:06:41
【问题描述】:

原始问题

我已经阅读了关于服务容器的“book”的每一页,但我仍然感到困惑,因为几乎每次我尝试使用 $this->container 时,事情似乎随机无法正常工作。例如,我正在我的自定义捆绑控制器中构建一个表单,遵循instructions

我的控制器像往常一样扩展基本控制器:

namespace Gutensite\ArticleBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Gutensite\ArticleBundle\Entity\Article;

class AdminEditController extends Controller
{

    public function indexAction() {


        $content = new Article();
        $form = $this->createFormBuilder($content)
             ->add('content', 'text');

        // same issue with the shortcut to the service which I created according the instructions
        // $form = $this->createForm('myForm', $myEntity)

        //...more code below...
    }
}

这会产生错误:

Fatal error: Call to a member function get() on a non-object in /vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/Controller.php on line 176

如果我们查看该文件的行号,我们会看到 Symfony 的代码:

public function createFormBuilder($data = null, array $options = array())
{
    return $this->container->get('form.factory')->createBuilder('form', $data, $options);
}

那么 为什么 symfony 自己的控制器无法访问 container->get() 函数?!

我做错了什么?

按照同样的思路,我不明白为什么有时我无法在我自己的控制器中通过 $this->container 访问容器(如果扩展框架控制器或者我通过在构造中传递它来引用它, ETC)。它似乎随机......


项目背景及代码结构

我正在构建一个将用户路线 (URL) 存储在数据库中的 CMS。所以我定义了一个路由,它将所有请求定向到我的主 CMS 控制器:

gutensite_cms_furl:
# Match Multiple Paths (the plain / path appears necessary)
path:     /
path:     /{furl}
defaults: { _controller: GutensiteCmsBundle:Init:index }
# Allow / in friendly urls, through more permissive regex
requirements:
    furl: .*

InitController 查找请求的 URL 并获取正确的 Route 实体,该实体指向一个 View 实体,该实体定义了为所请求的特定页面类型加载哪个 Bundle 和 Controller,例如/Admin/Article/Edit 的路由指向与 Article bundle 和 AdminEdit 控制器关联的内容类型,然后为该内容类型 (Gutensite\ArticleBundle\Controller\AdminEditController.php) 创建一个新对象并执行所需的功能。然后将必要的变量注入到主 ViewController 中,主 ViewController 被传递给模板以呈现到页面。

这个主控制器扩展了 symfony 控制器,我已经确认容器在这个控制器中是可访问的,例如$this->container->get('doctrine') 有效。

// Gutensite\CmsBundle\Controller\InitController.php
namespace Gutensite\CmsBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Gutensite\CmsBundle\Entity;

class InitController extends Controller
{
    public function indexAction(Request $request, $furl)
    {
        // Confirm container is accessible (yes it is)
        $test = $this->container->get('doctrine');

        // Look up the View Entity based on the Route Friendly URL: $furl
        $viewController = $this->container->get('gutensite_cms.view');
        $viewController->findView($furl, $siteId);

        // Load the Requested Bundle and Controller for this View
    $path = $viewController->view->namespace_controller."\\".$viewController->view->controller;
    $content = new $path;
        // Execute the main function for this content type controller, which adds variables back into the $viewController to be passed to the template.
    $content->indexAction($viewController);

        return $this->render(
        $viewController->view->bundle_shortcut.'::'.$viewController->view->getTemplatesLayout(),
            array('view' => $viewController)
    );


    }
}

仅供参考,ViewController 被定义为全局服务:

services:
    gutensite_cms.view:
        class: Gutensite\CmsBundle\Controller\ViewController
        arguments: [ "@service_container" ]

然后下面是Gutensite/CmsBundle/Controller/ViewController.php的简化版

namespace Gutensite\CmsBundle\Controller;
use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;

class ViewController
{

    protected $container;
    public $routing;
    public $view;

    public function __construct(Container $container) {
        $this->container = $container;
    }

    public function findView($furl, $siteId=NULL) {
        $em = $this->container->get('doctrine')->getManager();
        $this->routing = $em->getRepository('GutensiteCmsBundle:Routing\Routing')->findOneBy(
            array('furl'=>$furl, 'siteId'=>$siteId)
        );

        if(empty($this->routing)) return false;

        // If any redirects are set, don't bother getting view
        if(!empty($this->routing->getRedirect())) return FALSE;

        // If there is not view associated with route
        if(empty($this->routing->getView())) return FALSE;

        $this->view = $this->routing->getView();
        $this->setDefaults();
    }
}

回到InitController.php,我们检索了视图对象并加载了正确的包和控制器函数。在这种情况下,它加载了 `Gutensite\ArticleBundle\Controller\AdminEditController.php,这是我们无法访问服务容器的地方。

namespace Gutensite\ArticleBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Gutensite\ArticleBundle\Entity\Article;

class AdminEditController extends Controller
{

    protected $request;

    public function __contstruct(Request $request) {
        $this->request = $request;
}


public function indexAction($view)
    {
    // TEST: Test if I have access to container (I do not)
    //$doctrine = $this->container->get('doctrine');

        // This loads createForm() function from the Symfony Controller, but that controller then doesn't have access to container either.
        $form = $this->createForm('view', $content);

    }
}

更具体的问题

所以我假设如果你扩展 Symfony 控制器,它本身扩展了 ContainerAware,那么该对象将“知道容器”。但显然情况并非如此。这就是我需要更好地理解的。我认为必须以某种方式手动注入容器,但为什么呢?那是标准方法吗?

【问题讨论】:

  • 你能显示这个控制器的路由信息​​吗?
  • 因为您的路由使用服务 ID 指定控制器,并且您没有将容器注入控制器。因此请求查看您的路由信息​​。只需要对你的 services.yml 文件做一个简单的调整。
  • @WouterJ 我正在使用 Symfony 重建 CMS 的 2.0,因此有必要在数据库中动态定义路由。我有一个路由,它加载我的 Gutensite\CmsBundle\Controller\InitController.php,它扩展了框架控制器。然后它会在一个 Routes 表中查找,该表可以有一个或多个 URL,这些 URL 最终指向一个视图对象,该对象定义了要为所请求的页面类型加载的适当包和控制器。这行得通。在我的控制器中,我可以访问像 $this->createFormBuilder() 这样的控制器函数,这样容器就可以访问了。
  • @Cerad 你说我的“路由是使用服务ID指定控制器”?你是从哪里推断出来的,这是什么意思?我的控制器扩展了框架控制器,为什么我需要将容器注入其中?这难道不是扩展 Symfony 控制器的要点之一,它本身扩展了 ContainerAware 吗?正如我所提到的,在我的控制器中,容器似乎是可访问的,但在 symfony 控制器中,容器不是......
  • 我根据症状推断。你是正确的,默认情况下,你需要做的就是从基本的 symfony 控制器扩展。

标签: php symfony containers


【解决方案1】:

好的。您认为仅创建对象 ContainerAware 会自动导致容器被注入的假设是不正确的。 PHP new 运算符对依赖项一无所知。依赖注入容器的工作是自动注入东西。当然,您并没有使用容器来创建您的控制器。

很容易修复:

$path = $viewController->view->namespace_controller."\\".$viewController->view->controller;
$content = new $path;
$content->setContainer($this->container);
$content->indexAction($request,$viewController);

我并没有真正遵循你的流程。视图对我来说似乎有点倒退,但我相信你可以看到容器在何处以及如何注入 Symfony 控制器。不要在依赖于容器的控制器构造函数中做任何事情。

================================================ ================

您可以使用服务容器,而不是使用 new 运算符。

$contentServiceId = $viewController->view->contentServiceId;
$content = $this->container->get($contentServiceId);
$content->indexAction($request,$viewController);

不要让您查看返回类名,而是让它返回一个服务 ID。然后你在 services.yml 中配置你的控制器,然后就可以了。这个食谱条目可能会有所帮助:http://symfony.com/doc/current/cookbook/controller/service.html

================================================ ===============

ContainerAware 所做的只是让 Symfony DependencyInjectContainer 注入容器。而已。一点也不差。你可以考虑阅读这里:http://symfony.com/doc/current/components/dependency_injection/index.html 只是为了了解依赖注入和依赖注入容器的基本概念。

【讨论】:

  • 如果您看到更好的方法,我也希望得到有关如何更好地构建我的“流程”的反馈。我将这个项目作为一个学习机会,当我正确学习 Symfony 时,不可避免地会多次重构所有内容。
  • 我尝试在我的 $content 类中创建 setContainer() 但出现错误,现在我看到 setContainer() 是扩展 Symfony 控制器中的现有函数。所以这一定是一种普遍的做法。那确实成功地注入了容器。太棒了(鼓掌)。
  • 基本上是的。你可以看看 Symfony\Componen\HttpKernel\HttpKernel::handleRaw 来更好地理解默认的 Symfony 流程。控制器解析器类负责注入容器。
  • 最后,您可能应该将 InitController 替换为内核请求侦听器。侦听器将负责在数据库中查找 $furl 并设置 $request::_controller 属性。此时,默认的 Symfony 实现可以接管。但是您需要花时间了解 Symfony 如何将请求处理为响应,这样才有意义。
  • 可能不会。最终,您所描述的大部分内容可能会进入一个或多个听众。但只要你在进步,我就会沿着你现在的道路继续前进。
猜你喜欢
  • 1970-01-01
  • 2019-07-12
  • 1970-01-01
  • 2014-01-06
  • 2017-11-10
  • 1970-01-01
  • 1970-01-01
  • 2017-11-18
  • 1970-01-01
相关资源
最近更新 更多