【发布时间】: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