发布于 2015-08-27 16:29:17 | 289 次阅读 | 评论: 0 | 来源: 网络整理
Looks like you want to understand how Symfony works and how to extend it. That makes me very happy! This section is an in-depth explanation of the Symfony internals.
注解
You only need to read this section if you want to understand how Symfony works behind the scenes, or if you want to extend Symfony.
The Symfony code is made of several independent layers. Each layer is built on top of the previous one.
小技巧
Autoloading is not managed by the framework directly; it’s done by using Composer’s autoloader (vendor/autoload.php
), which is included in the app/autoload.php
file.
The deepest level is the HttpFoundation
component. HttpFoundation provides the main objects needed to deal with HTTP. It is an object-oriented abstraction of some native PHP functions and variables:
Request
class abstracts the main PHP global variables like $_GET
, $_POST
, $_COOKIE
, $_FILES
, and $_SERVER
;Response
class abstracts some PHP functions like header()
, setcookie()
, and echo
;Session
class and SessionStorageInterface
interface abstract session management session_*()
functions.注解
Read more about the HttpFoundation component.
On top of HttpFoundation is the HttpKernel
component. HttpKernel handles the dynamic part of HTTP; it is a thin wrapper on top of the Request and Response classes to standardize the way requests are handled. It also provides extension points and tools that makes it the ideal starting point to create a Web framework without too much overhead.
It also optionally adds configurability and extensibility, thanks to the DependencyInjection component and a powerful plugin system (bundles).
参见
Read more about the HttpKernel component, Dependency Injection and Bundles.
The FrameworkBundle
bundle is the bundle that ties the main components and libraries together to make a lightweight and fast MVC framework. It comes with a sensible default configuration and conventions to ease the learning curve.
The HttpKernel
class is the central class of Symfony and is responsible for handling client requests. Its main goal is to “convert” a Request
object to a Response
object.
Every Symfony Kernel implements HttpKernelInterface
:
function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true)
To convert a Request to a Response, the Kernel relies on a “Controller”. A Controller can be any valid PHP callable.
The Kernel delegates the selection of what Controller should be executed to an implementation of ControllerResolverInterface
:
public function getController(Request $request); public function getArguments(Request $request, $controller);
The getController()
method returns the Controller (a PHP callable) associated with the given Request. The default implementation (ControllerResolver
) looks for a _controller
request attribute that represents the controller name (a “class::method” string, like BundleBlogBundlePostController:indexAction
).
小技巧
The default implementation uses the RouterListener
to define the _controller
Request attribute (see kernel.request Event).
The getArguments()
method returns an array of arguments to pass to the Controller callable. The default implementation automatically resolves the method arguments, based on the Request attributes.
Matching Controller Method Arguments from Request Attributes
For each method argument, Symfony tries to get the value of a Request attribute with the same name. If it is not defined, the argument default value is used if defined:
// Symfony will look for an 'id' attribute (mandatory) // and an 'admin' one (optional) public function showAction($id, $admin = true) { // ... }
The handle()
method takes a Request
and always returns a Response
. To convert the Request
, handle()
relies on the Resolver and an ordered chain of Event notifications (see the next section for more information about each Event):
kernel.request
event is notified – if one of the listeners returns a Response
, it jumps to step 8 directly;kernel.controller
event can now manipulate the Controller callable the way they want (change it, wrap it, ...);Response
, listeners of the kernel.view
event can convert the Controller return value to a Response
;kernel.response
event can manipulate the Response
(content and headers);kernel.terminate
event can perform tasks after the Response has been served.If an Exception is thrown during processing, the kernel.exception
is notified and listeners are given a chance to convert the Exception to a Response. If that works, the kernel.response
event is notified; if not, the Exception is re-thrown.
If you don’t want Exceptions to be caught (for embedded requests for instance), disable the kernel.exception
event by passing false
as the third argument to the handle()
method.
At any time during the handling of a request (the ‘master’ one), a sub-request can be handled. You can pass the request type to the handle()
method (its second argument):
HttpKernelInterface::MASTER_REQUEST
;HttpKernelInterface::SUB_REQUEST
.The type is passed to all events and listeners can act accordingly (some processing must only occur on the master request).
Each event thrown by the Kernel is a subclass of KernelEvent
. This means that each event has access to the same basic information:
getRequestType()
HttpKernelInterface::MASTER_REQUEST
or HttpKernelInterface::SUB_REQUEST
).isMasterRequest()
getKernel()
getRequest()
Request
being handled.isMasterRequest()
¶The isMasterRequest()
method allows listeners to check the type of the request. For instance, if a listener must only be active for master requests, add the following code at the beginning of your listener method:
use SymfonyComponentHttpKernelHttpKernelInterface; if (!$event->isMasterRequest()) { // return immediately return; }
小技巧
If you are not yet familiar with the Symfony EventDispatcher, read the EventDispatcher component documentation section first.
kernel.request
Event¶Event Class: GetResponseEvent
The goal of this event is to either return a Response
object immediately or setup variables so that a Controller can be called after the event. Any listener can return a Response
object via the setResponse()
method on the event. In this case, all other listeners won’t be called.
This event is used by the FrameworkBundle to populate the _controller
Request
attribute, via the RouterListener
. RequestListener uses a RouterInterface
object to match the Request
and determine the Controller name (stored in the _controller
Request
attribute).
参见
Read more on the kernel.request event.
kernel.controller
Event¶Event Class: FilterControllerEvent
This event is not used by the FrameworkBundle, but can be an entry point used to modify the controller that should be executed:
use SymfonyComponentHttpKernelEventFilterControllerEvent; public function onKernelController(FilterControllerEvent $event) { $controller = $event->getController(); // ... // the controller can be changed to any PHP callable $event->setController($controller); }
参见
Read more on the kernel.controller event.
kernel.view
Event¶Event Class: GetResponseForControllerResultEvent
This event is not used by the FrameworkBundle, but it can be used to implement a view sub-system. This event is called only if the Controller does not return a Response
object. The purpose of the event is to allow some other return value to be converted into a Response
.
The value returned by the Controller is accessible via the getControllerResult
method:
use SymfonyComponentHttpKernelEventGetResponseForControllerResultEvent; use SymfonyComponentHttpFoundationResponse; public function onKernelView(GetResponseForControllerResultEvent $event) { $val = $event->getControllerResult(); $response = new Response(); // ... some how customize the Response from the return value $event->setResponse($response); }
参见
Read more on the kernel.view event.
kernel.response
Event¶Event Class: FilterResponseEvent
The purpose of this event is to allow other systems to modify or replace the Response
object after its creation:
public function onKernelResponse(FilterResponseEvent $event) { $response = $event->getResponse(); // ... modify the response object }
The FrameworkBundle registers several listeners:
ProfilerListener
WebDebugToolbarListener
ResponseListener
Content-Type
based on the request format.EsiListener
Surrogate-Control
HTTP header when the Response needs to be parsed for ESI tags.参见
Read more on the kernel.response event.
kernel.finish_request
Event¶Event Class: FinishRequestEvent
The purpose of this event is to handle tasks that should be performed after the request has been handled but that do not need to modify the response. Event listeners for the kernel.finish_request
event are called in both successful and exception cases.
kernel.terminate
Event¶Event Class: PostResponseEvent
The purpose of this event is to perform “heavier” tasks after the response was already served to the client.
参见
Read more on the kernel.terminate event.
kernel.exception
Event¶Event Class: GetResponseForExceptionEvent
The FrameworkBundle registers an ExceptionListener
that forwards the Request
to a given Controller (the value of the exception_listener.controller
parameter – must be in the class::method
notation).
A listener on this event can create and set a Response
object, create and set a new Exception
object, or do nothing:
use SymfonyComponentHttpKernelEventGetResponseForExceptionEvent; use SymfonyComponentHttpFoundationResponse; public function onKernelException(GetResponseForExceptionEvent $event) { $exception = $event->getException(); $response = new Response(); // setup the Response object based on the caught exception $event->setResponse($response); // you can alternatively set a new Exception // $exception = new Exception('Some special exception'); // $event->setException($exception); }
注解
As Symfony ensures that the Response status code is set to the most appropriate one depending on the exception, setting the status on the response won’t work. If you want to overwrite the status code (which you should not without a good reason), set the X-Status-Code
header:
return new Response( 'Error', Response::HTTP_NOT_FOUND, // ignored array('X-Status-Code' => Response::HTTP_OK) );
2.4 新版功能: Support for HTTP status code constants was introduced in Symfony 2.4.
参见
Read more on the kernel.exception event.
The EventDispatcher is a standalone component that is responsible for much of the underlying logic and flow behind a Symfony request. For more information, see the EventDispatcher component documentation.
When enabled, the Symfony profiler collects useful information about each request made to your application and store them for later analysis. Use the profiler in the development environment to help you to debug your code and enhance performance; use it in the production environment to explore problems after the fact.
You rarely have to deal with the profiler directly as Symfony provides visualizer tools like the Web Debug Toolbar and the Web Profiler. If you use the Symfony Standard Edition, the profiler, the web debug toolbar, and the web profiler are all already configured with sensible settings.
注解
The profiler collects information for all requests (simple requests, redirects, exceptions, Ajax requests, ESI requests; and for all HTTP methods and all formats). It means that for a single URL, you can have several associated profiling data (one per external request/response pair).
In the development environment, the web debug toolbar is available at the bottom of all pages. It displays a good summary of the profiling data that gives you instant access to a lot of useful information when something does not work as expected.
If the summary provided by the Web Debug Toolbar is not enough, click on the token link (a string made of 13 random characters) to access the Web Profiler.
注解
If the token is not clickable, it means that the profiler routes are not registered (see below for configuration information).
The Web Profiler is a visualization tool for profiling data that you can use in development to debug your code and enhance performance; but it can also be used to explore problems that occur in production. It exposes all information collected by the profiler in a web interface.
You don’t need to use the default visualizer to access the profiling information. But how can you retrieve profiling information for a specific request after the fact? When the profiler stores data about a Request, it also associates a token with it; this token is available in the X-Debug-Token
HTTP header of the Response:
$profile = $container->get('profiler')->loadProfileFromResponse($response); $profile = $container->get('profiler')->loadProfile($token);
小技巧
When the profiler is enabled but not the web debug toolbar, or when you want to get the token for an Ajax request, use a tool like Firebug to get the value of the X-Debug-Token
HTTP header.
Use the find()
method to access tokens based on some criteria:
// get the latest 10 tokens $tokens = $container->get('profiler')->find('', '', 10, '', ''); // get the latest 10 tokens for all URL containing /admin/ $tokens = $container->get('profiler')->find('', '/admin/', 10, '', ''); // get the latest 10 tokens for local requests $tokens = $container->get('profiler')->find('127.0.0.1', '', 10, '', ''); // get the latest 10 tokens for requests that happened between 2 and 4 days ago $tokens = $container->get('profiler') ->find('', '', 10, '4 days ago', '2 days ago');
If you want to manipulate profiling data on a different machine than the one where the information were generated, use the export()
and import()
methods:
// on the production machine $profile = $container->get('profiler')->loadProfile($token); $data = $profiler->export($profile); // on the development machine $profiler->import($data);
The default Symfony configuration comes with sensible settings for the profiler, the web debug toolbar, and the web profiler. Here is for instance the configuration for the development environment:
# load the profiler framework: profiler: { only_exceptions: false } # enable the web profiler web_profiler: toolbar: true intercept_redirects: true
<?xml version="1.0" encoding="UTF-8" ?> <container xmlns="http://symfony.com/schema/dic/services" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:webprofiler="http://symfony.com/schema/dic/webprofiler" xmlns:framework="http://symfony.com/schema/dic/symfony" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd http://symfony.com/schema/dic/webprofiler http://symfony.com/schema/dic/webprofiler/webprofiler-1.0.xsd http://symfony.com/schema/dic/symfony http://symfony.com/schema/dic/symfony/symfony-1.0.xsd"> <!-- load the profiler --> <framework:config> <framework:profiler only-exceptions="false" /> </framework:config> <!-- enable the web profiler --> <webprofiler:config toolbar="true" intercept-redirects="true" /> </container>
// load the profiler $container->loadFromExtension('framework', array( 'profiler' => array('only_exceptions' => false), )); // enable the web profiler $container->loadFromExtension('web_profiler', array( 'toolbar' => true, 'intercept_redirects' => true, ));
When only_exceptions
is set to true
, the profiler only collects data when an exception is thrown by the application.
When intercept_redirects
is set to true
, the web profiler intercepts the redirects and gives you the opportunity to look at the collected data before following the redirect.
If you enable the web profiler, you also need to mount the profiler routes:
_profiler: resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml" prefix: /_profiler
<?xml version="1.0" encoding="UTF-8" ?> <routes xmlns="http://symfony.com/schema/routing" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://symfony.com/schema/routing http://symfony.com/schema/routing/routing-1.0.xsd"> <import resource="@WebProfilerBundle/Resources/config/routing/profiler.xml" prefix="/_profiler" /> </routes>
use SymfonyComponentRoutingRouteCollection; $profiler = $loader->import( '@WebProfilerBundle/Resources/config/routing/profiler.xml' ); $profiler->addPrefix('/_profiler'); $collection = new RouteCollection(); $collection->addCollection($profiler);
As the profiler adds some overhead, you might want to enable it only under certain circumstances in the production environment. The only_exceptions
settings limits profiling to exceptions, but what if you want to get information when the client IP comes from a specific address, or for a limited portion of the website? You can use a Profiler Matcher, learn more about that in “How to Use Matchers to Enable the Profiler Conditionally”.