Skip to content

Commit

Permalink
[WebBundle] introduced a ControllerManager class
Browse files Browse the repository at this point in the history
  • Loading branch information
fabpot committed May 18, 2010
1 parent e715bc3 commit 3749c59
Show file tree
Hide file tree
Showing 5 changed files with 181 additions and 115 deletions.
4 changes: 2 additions & 2 deletions src/Symfony/Components/HttpKernel/HttpKernel.php
Expand Up @@ -150,7 +150,7 @@ protected function handleRaw(Request $request, $type = self::MASTER_REQUEST)
* Handles a request that need to be embedded.
*
* @param Request $request A Request instance
* @param Boolean $raw Whether to catch exceptions or not
* @param Boolean $raw Whether to catch exceptions or not
*
* @return string|false The Response content or false if there is a problem
*
Expand Down Expand Up @@ -182,7 +182,7 @@ protected function handleEmbedded(Request $request, $raw = false)
*
* @param Response $response A Response instance
* @param string $message A error message in case the response is not a Response object
* @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST, HttpKernelInterface::FORWARDED_REQUEST, or HttpKernelInterface::EMBEDDED_REQUEST)
* @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST, HttpKernelInterface::FORWARDED_REQUEST, or HttpKernelInterface::EMBEDDED_REQUEST)
*
* @return Response The filtered Response instance
*
Expand Down
150 changes: 150 additions & 0 deletions src/Symfony/Framework/WebBundle/Controller/ControllerManager.php
@@ -0,0 +1,150 @@
<?php

namespace Symfony\Framework\WebBundle\Controller;

use Symfony\Foundation\LoggerInterface;
use Symfony\Components\DependencyInjection\ContainerInterface;
use Symfony\Components\HttpKernel\HttpKernelInterface;

/*
* This file is part of the Symfony framework.
*
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

/**
* ControllerManager.
*
* @package Symfony
* @subpackage Framework_WebBundle
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
*/
class ControllerManager
{
protected $container;
protected $logger;

public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
{
$this->container = $container;
$this->logger = $logger;
}

/**
* Renders a Controller and returns the Response content.
*
* @param string $controller A controller name to execute (a string like BlogBundle:Post:index), or a relative URI
* @param array $options An array of options
*
* @return string The Response content
*/
public function render($controller, array $options = array())
{
$request = $this->container->getRequestService();

// controller or URI?
if (0 === strpos($controller, '/')) {
$subRequest = Request::create($controller, 'get', array(), $request->cookies->all(), array(), $request->server->all());
} else {
$options['path']['_controller'] = $controller;
$options['path']['_format'] = $request->getRequestFormat();
$subRequest = $request->duplicate($options['query'], null, $options['path']);
}

try {
return $this->container->getKernelService()->handle($subRequest, HttpKernelInterface::EMBEDDED_REQUEST, true);
} catch (\Exception $e) {
if ($options['alt']) {
$alt = $options['alt'];
unset($options['alt']);
$options['path'] = isset($alt[1]) ? $alt[1] : array();
$options['query'] = isset($alt[2]) ? $alt[2] : array();

return $this->render($alt[0], $options);
}

if (!$options['ignore_errors']) {
throw $e;
}
}
}

/**
* Creates the Controller instance associated with the controller string
*
* @param string $controller A controller name (a string like BlogBundle:Post:index)
*
* @return array An array composed of the Controller instance and the Controller method
*
* @throws \InvalidArgumentException|\LogicException If the controller can't be found
*/
public function findController($controller)
{
list($bundle, $controller, $action) = explode(':', $controller);
$class = null;
$logs = array();
foreach (array_keys($this->container->getParameter('kernel.bundle_dirs')) as $namespace) {
$try = $namespace.'\\'.$bundle.'\\Controller\\'.$controller.'Controller';
if (!class_exists($try)) {
if (null !== $this->logger) {
$logs[] = sprintf('Failed finding controller "%s:%s" from namespace "%s" (%s)', $bundle, $controller, $namespace, $try);
}
} else {
if (!in_array($namespace.'\\'.$bundle.'\\Bundle', array_map(function ($bundle) { return get_class($bundle); }, $this->container->getKernelService()->getBundles()))) {
throw new \LogicException(sprintf('To use the "%s" controller, you first need to enable the Bundle "%s" in your Kernel class.', $try, $namespace.'\\'.$bundle));
}

$class = $try;

break;
}
}

if (null === $class) {
if (null !== $this->logger) {
foreach ($logs as $log) {
$this->logger->info($log);
}
}

throw new \InvalidArgumentException(sprintf('Unable to find controller "%s:%s".', $bundle, $controller));
}

$controller = new $class($this->container);

$method = $action.'Action';
if (!method_exists($controller, $method)) {
throw new \InvalidArgumentException(sprintf('Method "%s::%s" does not exist.', $class, $method));
}

if (null !== $this->logger) {
$this->logger->info(sprintf('Using controller "%s::%s"%s', $class, $method, isset($file) ? sprintf(' from file "%s"', $file) : ''));
}

return array($controller, $method);
}

/**
* @throws \RuntimeException When value for argument given is not provided
*/
public function getMethodArguments(array $path, $controller, $method)
{
$r = new \ReflectionObject($controller);

$arguments = array();
foreach ($r->getMethod($method)->getParameters() as $param) {
if (array_key_exists($param->getName(), $path)) {
$arguments[] = $path[$param->getName()];
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = $param->getDefaultValue();
} else {
throw new \RuntimeException(sprintf('Controller "%s::%s()" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $controller, $method, $param->getName()));
}
}

return $arguments;
}
}
34 changes: 1 addition & 33 deletions src/Symfony/Framework/WebBundle/Helper/ActionsHelper.php
Expand Up @@ -80,39 +80,7 @@ public function render($controller, array $options = array())
$options['path'] = Escaper::unescape($options['path']);
$options['query'] = Escaper::unescape($options['query']);

return $this->doRender($controller, $options);
}

protected function doRender($controller, array $options = array())
{
// controller or URI?
$request = $this->container->getRequestService();
if (0 === strpos($controller, '/')) {
// URI
$subRequest = Request::create($controller, 'get', array(), $request->cookies->all(), array(), $request->server->all());
} else {
// controller
$options['path']['_controller'] = $controller;
$options['path']['_format'] = $request->getRequestFormat();
$subRequest = $request->duplicate($options['query'], null, $options['path']);
}

try {
return $this->container->getKernelService()->handle($subRequest, HttpKernelInterface::EMBEDDED_REQUEST, true);
} catch (\Exception $e) {
if ($options['alt']) {
$alt = $options['alt'];
unset($options['alt']);
$options['path'] = isset($alt[1]) ? $alt[1] : array();
$options['query'] = isset($alt[2]) ? $alt[2] : array();

return $this->doRender($alt[0], $options);
}

if (!$options['ignore_errors']) {
throw $e;
}
}
return $this->container->getControllerManagerService()->render($controller, $options);
}

/**
Expand Down
99 changes: 20 additions & 79 deletions src/Symfony/Framework/WebBundle/Listener/ControllerLoader.php
Expand Up @@ -2,8 +2,9 @@

namespace Symfony\Framework\WebBundle\Listener;

use Symfony\Framework\WebBundle\Controller\ControllerManager;
use Symfony\Foundation\LoggerInterface;
use Symfony\Components\DependencyInjection\ContainerInterface;
use Symfony\Components\EventDispatcher\EventDispatcher;
use Symfony\Components\EventDispatcher\Event;

/*
Expand All @@ -25,108 +26,48 @@
*/
class ControllerLoader
{
protected $container;
protected $manager;
protected $dispatcher;
protected $logger;

public function __construct(ContainerInterface $container, LoggerInterface $logger = null)
public function __construct(EventDispatcher $dispatcher, ControllerManager $manager, LoggerInterface $logger = null)
{
$this->container = $container;
$this->dispatcher = $dispatcher;
$this->manager = $manager;
$this->logger = $logger;
}

public function register()
{
$this->container->getEventDispatcherService()->connect('core.load_controller', array($this, 'resolve'));
$this->dispatcher->connect('core.load_controller', array($this, 'resolve'));
}

/**
* Creates the Controller associated with the given Request.
*
* @param Event $event An Event instance
*
* @return Boolean true if the controller has been found, false otherwise
*/
public function resolve(Event $event)
{
$request = $event->getParameter('request');

if (!$controller = $request->path->get('_controller')) {
if (null !== $this->logger) {
$this->logger->err('Unable to look for the controller as the _controller parameter is missing');
$this->logger->err('Unable to look for the controller as the "_controller" parameter is missing');
}

return false;
}

$controller = $this->findController($controller);
$controller[0]->setRequest($request);
list($controller, $method) = $this->manager->findController($controller);
$controller->setRequest($request);

$r = new \ReflectionObject($controller[0]);
$arguments = $this->getMethodArguments($r->getMethod($controller[1]), $request->path->all(), sprintf('%s::%s()', get_class($controller[0]), $controller[1]));
$arguments = $this->manager->getMethodArguments($request->path->all(), $controller, $method);

$event->setReturnValue(array($controller, $arguments));
$event->setReturnValue(array(array($controller, $method), $arguments));

return true;
}

/**
* @throws \InvalidArgumentException|\LogicException If controller can't be found
*/
public function findController($controller)
{
list($bundle, $controller, $action) = explode(':', $controller);
$class = null;
$logs = array();
foreach (array_keys($this->container->getParameter('kernel.bundle_dirs')) as $namespace) {
$try = $namespace.'\\'.$bundle.'\\Controller\\'.$controller.'Controller';
if (!class_exists($try)) {
if (null !== $this->logger) {
$logs[] = sprintf('Failed finding controller "%s:%s" from namespace "%s" (%s)', $bundle, $controller, $namespace, $try);
}
} else {
if (!in_array($namespace.'\\'.$bundle.'\\Bundle', array_map(function ($bundle) { return get_class($bundle); }, $this->container->getKernelService()->getBundles()))) {
throw new \LogicException(sprintf('To use the "%s" controller, you first need to enable the Bundle "%s" in your Kernel class.', $try, $namespace.'\\'.$bundle));
}

$class = $try;

break;
}
}

if (null === $class) {
if (null !== $this->logger) {
foreach ($logs as $log) {
$this->logger->info($log);
}
}

throw new \InvalidArgumentException(sprintf('Unable to find controller "%s:%s".', $bundle, $controller));
}

$controller = new $class($this->container);

$method = $action.'Action';
if (!method_exists($controller, $method)) {
throw new \InvalidArgumentException(sprintf('Method "%s::%s" does not exist.', $class, $method));
}

if (null !== $this->logger) {
$this->logger->info(sprintf('Using controller "%s::%s"%s', $class, $method, isset($file) ? sprintf(' from file "%s"', $file) : ''));
}

return array($controller, $method);
}

/**
* @throws \RuntimeException When value for argument given is not provided
*/
public function getMethodArguments(\ReflectionFunctionAbstract $r, array $parameters, $controller)
{
$arguments = array();
foreach ($r->getParameters() as $param) {
if (array_key_exists($param->getName(), $parameters)) {
$arguments[] = $parameters[$param->getName()];
} elseif ($param->isDefaultValueAvailable()) {
$arguments[] = $param->getDefaultValue();
} else {
throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument (because there is no default value or because there is a non optional argument after this one).', $controller, $param->getName()));
}
}

return $arguments;
}
}
9 changes: 8 additions & 1 deletion src/Symfony/Framework/WebBundle/Resources/config/web.xml
Expand Up @@ -6,6 +6,7 @@

<parameters>
<parameter key="request_parser.class">Symfony\Framework\WebBundle\Listener\RequestParser</parameter>
<parameter key="controller_manager.class">Symfony\Framework\WebBundle\Controller\ControllerManager</parameter>
<parameter key="controller_loader.class">Symfony\Framework\WebBundle\Listener\ControllerLoader</parameter>
<parameter key="router.class">Symfony\Components\Routing\Router</parameter>
<parameter key="response_filter.class">Symfony\Framework\WebBundle\Listener\ResponseFilter</parameter>
Expand All @@ -17,9 +18,15 @@
</parameters>

<services>
<service id="controller_manager" class="%controller_manager.class%">
<argument type="service" id="service_container" />
<argument type="service" id="logger" on-invalid="ignore" />
</service>

<service id="controller_loader" class="%controller_loader.class%">
<annotation name="kernel.listener" event="core.load_controller" method="resolve" />
<argument type="service" id="service_container" />
<argument type="service" id="event_dispatcher" />
<argument type="service" id="controller_manager" />
<argument type="service" id="logger" on-invalid="ignore" />
</service>

Expand Down

0 comments on commit 3749c59

Please sign in to comment.