发布于 2015-08-27 16:43:10 | 153 次阅读 | 评论: 0 | 来源: 网络整理
In the book, you’ve learned how easily a controller can be used when it
extends the base
Controller
class. While
this works fine, controllers can also be specified as services.
注解
Specifying a controller as a service takes a little bit more work. The primary advantage is that the entire controller or any services passed to the controller can be modified via the service container configuration. This is especially useful when developing an open-source bundle or any bundle that will be used in many different projects.
A second advantage is that your controllers are more “sandboxed”. By looking at the constructor arguments, it’s easy to see what types of things this controller may or may not do. And because each dependency needs to be injected manually, it’s more obvious (i.e. if you have many constructor arguments) when your controller is becoming too big. The recommendation from the best practices is also valid for controllers defined as services: Avoid putting your business logic into the controllers. Instead, inject services that do the bulk of the work.
So, even if you don’t specify your controllers as services, you’ll likely see this done in some open-source Symfony bundles. It’s also important to understand the pros and cons of both approaches.
A controller can be defined as a service in the same way as any other class. For example, if you have the following simple controller:
// src/AppBundle/Controller/HelloController.php
namespace AppBundleController;
use SymfonyComponentHttpFoundationResponse;
class HelloController
{
public function indexAction($name)
{
return new Response('<html><body>Hello '.$name.'!</body></html>');
}
}
Then you can define it as a service as follows:
# app/config/services.yml
services:
app.hello_controller:
class: AppBundleControllerHelloController
<!-- app/config/services.xml -->
<services>
<service id="app.hello_controller" class="AppBundleControllerHelloController" />
</services>
// app/config/services.php
use SymfonyComponentDependencyInjectionDefinition;
$container->setDefinition('app.hello_controller', new Definition(
'AppBundleControllerHelloController'
));
To refer to a controller that’s defined as a service, use the single colon (:)
notation. For example, to forward to the indexAction()
method of the service
defined above with the id app.hello_controller
:
$this->forward('app.hello_controller:indexAction', array('name' => $name));
注解
You cannot drop the Action
part of the method name when using this
syntax.
You can also route to the service by using the same notation when defining
the route _controller
value:
# app/config/routing.yml
hello:
path: /hello
defaults: { _controller: app.hello_controller:indexAction }
<!-- app/config/routing.xml -->
<route id="hello" path="/hello">
<default key="_controller">app.hello_controller:indexAction</default>
</route>
// app/config/routing.php
$collection->add('hello', new Route('/hello', array(
'_controller' => 'app.hello_controller:indexAction',
)));
小技巧
You can also use annotations to configure routing using a controller defined as a service. See the FrameworkExtraBundle documentation for details.
2.6 新版功能: If your controller service implements the __invoke
method, you can simply refer to the service id
(app.hello_controller
).
When using a controller defined as a service, it will most likely not extend
the base Controller
class. Instead of relying on its shortcut methods,
you’ll interact directly with the services that you need. Fortunately, this is
usually pretty easy and the base Controller class source code is a great
source on how to perform many common tasks.
For example, if you want to render a template instead of creating the Response
object directly, then your code would look like this if you were extending
Symfony’s base controller:
// src/AppBundle/Controller/HelloController.php
namespace AppBundleController;
use SymfonyBundleFrameworkBundleControllerController;
class HelloController extends Controller
{
public function indexAction($name)
{
return $this->render(
'AppBundle:Hello:index.html.twig',
array('name' => $name)
);
}
}
If you look at the source code for the render
function in Symfony’s
base Controller class, you’ll see that this method actually uses the
templating
service:
public function render($view, array $parameters = array(), Response $response = null)
{
return $this->container->get('templating')->renderResponse($view, $parameters, $response);
}
In a controller that’s defined as a service, you can instead inject the templating
service and use it directly:
// src/AppBundle/Controller/HelloController.php
namespace AppBundleController;
use SymfonyBundleFrameworkBundleTemplatingEngineInterface;
use SymfonyComponentHttpFoundationResponse;
class HelloController
{
private $templating;
public function __construct(EngineInterface $templating)
{
$this->templating = $templating;
}
public function indexAction($name)
{
return $this->templating->renderResponse(
'AppBundle:Hello:index.html.twig',
array('name' => $name)
);
}
}
The service definition also needs modifying to specify the constructor argument:
# app/config/services.yml
services:
app.hello_controller:
class: AppBundleControllerHelloController
arguments: ["@templating"]
<!-- app/config/services.xml -->
<services>
<service id="app.hello_controller" class="AppBundleControllerHelloController">
<argument type="service" id="templating"/>
</service>
</services>
// app/config/services.php
use SymfonyComponentDependencyInjectionDefinition;
use SymfonyComponentDependencyInjectionReference;
$container->setDefinition('app.hello_controller', new Definition(
'AppBundleControllerHelloController',
array(new Reference('templating'))
));
Rather than fetching the templating
service from the container, you can
inject only the exact service(s) that you need directly into the controller.
注解
This does not mean that you cannot extend these controllers from your own base controller. The move away from the standard base controller is because its helper methods rely on having the container available which is not the case for controllers that are defined as services. It may be a good idea to extract common code into a service that’s injected rather than place that code into a base controller that you extend. Both approaches are valid, exactly how you want to organize your reusable code is up to you.
This list explains how to replace the convenience methods of the base controller:
createForm()
(service: form.factory
)$formFactory->create($type, $data, $options);
createFormBuilder()
(service: form.factory
)$formFactory->createBuilder('form', $data, $options);
createNotFoundException()
new NotFoundHttpException($message, $previous);
forward()
(service: http_kernel
)$httpKernel->forward($controller, $path, $query);
generateUrl()
(service: router
)$router->generate($route, $params, $absolute);
getDoctrine()
(service: doctrine
)
Simply inject doctrine instead of fetching it from the container
getUser()
(service: security.token_storage
)$user = null;
$token = $tokenStorage->getToken();
if (null !== $token && is_object($token->getUser())) {
$user = $token->getUser();
}
isGranted()
(service: security.authorization_checker
)$authChecker->isGranted($attributes, $object);
redirect()
use SymfonyComponentHttpFoundationRedirectResponse;
return new RedirectResponse($url, $status);
render()
(service: templating
)$templating->renderResponse($view, $parameters, $response);
renderView()
(service: templating
)$templating->render($view, $parameters);
stream()
(service: templating
)use SymfonyComponentHttpFoundationStreamedResponse;
$templating = $this->templating;
$callback = function () use ($templating, $view, $parameters) {
$templating->stream($view, $parameters);
}
return new StreamedResponse($callback);
小技巧
getRequest
has been deprecated. Instead, have an argument to your
controller action method called Request $request
. The order of the
parameters is not important, but the typehint must be provided.