symfony源碼分析之框架主流程

這是基於 symfony3.3.0版本的源代碼分析,主要包含以下部分:

  1. 框架主流程

  2. 容器生成及使用

  3. 路由生成

  4. 配置文件加載

  5. 事件委派

在對源代碼進行分析的時候,使用phpstrom配合xdebug擴展進行斷點調試,對代碼分析以及梳理起到了很大的幫助。

1 調用過程

web/app.php

$kernel = new AppKernel('prod', false);
$response = $kernel->handle($request);


Symfony\Component\HttpKernel\Kernel

public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
    if (false === $this->booted) {
    	// 初始化 容器 與 Bundle
        $this->boot();
    }
    // 這裏通過容器的形式獲取 Symfony\Component\HttpKernel\HttpKernel 並調用 handle方法 
    return $this->getHttpKernel()->handle($request, $type, $catch);
}


Symfony\Component\HttpKernel\HttpKernel

private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
    $this->requestStack->push($request);

// 匹配路由 查詢需要執行的控制器
$event = new GetResponseEvent($this, $request, $type);
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event);

// 獲取控制器的實例以及要執行的方法
if (false === $controller = $this->resolver->getController($request)) {
    throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo()));
}
// 這裏的是爲了觸發事件 可以在事件委派的章節具體查看
$event = new FilterControllerEvent($this, $controller, $request, $type);
$this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);
$controller = $event->getController();

// 對控制器要執行的方法進行反射 獲取方法中需要的參數 並通過容器來獲取實例化的對象返回
$arguments = $this->argumentResolver->getArguments($request, $controller);

// 這裏的是爲了觸發事件 可以在事件委派的章節具體查看
$event = new FilterControllerArgumentsEvent($this, $controller, $arguments, $request, $type);
$this->dispatcher->dispatch(KernelEvents::CONTROLLER_ARGUMENTS, $event);
$controller = $event->getController();
$arguments = $event->getArguments();

// call controller
//這裏執行控制器
$response = call_user_func_array($controller, $arguments);

if (!$response instanceof Response) {
	// 當控制器返回的不是Response的話當作view來渲染處理
}

return $this->filterResponse($response, $request, $type);
}


2 主流程分析

對主流程代碼進行精簡,忽略事件委派的代碼,流程如下

// 1. 匹配路由 查詢需要執行的控制器
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
// 2. 獲取控制器的實例以及要執行的方法
$controller = $this->resolver->getController($request)
// 3. 對控制器要執行的方法進行反射 獲取方法中需要的參數 並通過容器來獲取實例化的對象返回
$arguments = $this->argumentResolver->getArguments($request, $controller);
// 4. 這裏執行控制器
$response = call_user_func_array($controller, $arguments);


2.1 匹配路由 查詢需要執行的控制器

這裏主要是觸發了一個事件KernelEvents::REQUEST, 這個事件中的一個handler:Symfony\Component\HttpKernel\EventListener\RouterListener,進行了路由 、控制器的分析查詢,如果路由沒有生成還會生成路由代碼。

# Symfony\Component\HttpKernel\EventListener\RouterListener
public function onKernelRequest(GetResponseEvent $event)
{
	// 路由已經解析過的話就直接返回
    if ($request->attributes->has('_controller')) {
        return;
    }

    try {
        if ($this->matcher instanceof RequestMatcherInterface) {
        	// 匹配路由 獲取信息
            $parameters = $this->matcher->matchRequest($request);
        } else {
            $parameters = $this->matcher->match($request->getPathInfo());
        }
        //將分析出來的參數信息 控制器 方法 路由 添加到 request 的 attributes屬性中
        // $parameters = [
        // 	'_controller' => "控制器::方法",
        // 	'_route' =''
        // ]
        $request->attributes->add($parameters);
        unset($parameters['_route'], $parameters['_controller']);
        $request->attributes->set('_route_params', $parameters);
    } catch (ResourceNotFoundException $e) {
    	// ......
    } catch (MethodNotAllowedException $e) {
    	// ......
    }
}

路由匹配的代碼分析,框架會將路由信息分析出來並存儲在 緩存文件 var/prod/appProdProjectContainerUrlMatcher.php 中,具體的路由生成代碼可以到 路由代碼生成分析 具體查看

# Symfony\Bundle\FrameworkBundle\Routing\Router
public function getRouteCollection()
{
    if (null === $this->collection) {
    	// 通過容器獲取 routing.loader 對應的對象並調用方法 load 
    	// Symfony\Bundle\FrameworkBundle\Routing\DelegatingLoader::load 
    	// 參數 
    	//		$this->resource -> app/config/routing.yml 
    	//		$this->options['resource_type'] -> null 
        $this->collection = $this->container->get('routing.loader')->load($this->resource, $this->options['resource_type']);
        $this->resolveParameters($this->collection);
        $this->collection->addResource(new ContainerParametersResource($this->collectedParameters));
    }

    return $this->collection;
}

# Syfmony\Component\Routing\Router\Router
public function matchRequest(Request $request)
{
    $matcher = $this->getMatcher();
    if (!$matcher instanceof RequestMatcherInterface) {
        // fallback to the default UrlMatcherInterface
        return $matcher->match($request->getPathInfo());
    }

    return $matcher->matchRequest($request);
}

public function getMatcher()
{
    // $this->getConfigCacheFactory() -> Symfony\Component\Config\ResourceCheckerConfigCacheFactory
    // 獲取存儲路由信息的文件 var/prod/appProdProjectContainerUrlMatcher.php
    $cache = $this->getConfigCacheFactory()->cache($this->options['cache_dir'].'/'.$this->options['matcher_cache_class'].'.php',
        //當路由文件沒有生成的時候 這個匿名方法執行,通過配置文件生成路由關係且存儲到緩存文件
        function (ConfigCacheInterface $cache) {
            // ......
        }
    );

    //這裏加載並實例化了緩存的路由信息 var/cache/dev/app[ENV]DebugProjectContainerUrlMatcher.php
    require_once $cache->getPath();
    return $this->matcher = new $this->options['matcher_cache_class']($this->context);
}


2.2 獲取控制器的實例以及要執行的方法

此處代碼的調用連如下

Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver extends 
Symfony\Component\HttpKernel\Controller\ContainerControllerResolver extends 
Symfony\Component\HttpKernel\Controller\ControllerResolver


最終執行的核心代碼如下:

# Symfony\Component\HttpKernel\Controller\ControllerResolver
public function getController(Request $request)
{
	// 這裏的變量 $controller 就是從$request 的 attributes屬性中獲取的,這個屬性的數據是 事件 KernelEvents::CONTROLLER中的handler RouterListener 設置的
	// 下面的代碼邏輯主要是對控制器不同類型的處理 匿名回調 數組 對象 等
    if (!$controller = $request->attributes->get('_controller')) {
        if (null !== $this->logger) {
            $this->logger->warning('Unable to look for the controller as the "_controller" parameter is missing.');
        }

        return false;
    }

    if (is_array($controller)) {
        return $controller;
    }

    if (is_object($controller)) {
        if (method_exists($controller, '__invoke')) {
            return $controller;
        }

        throw new \InvalidArgumentException(sprintf('Controller "%s" for URI "%s" is not callable.', get_class($controller), $request->getPathInfo()));
    }

    if (false === strpos($controller, ':')) {
        if (method_exists($controller, '__invoke')) {
            return $this->instantiateController($controller);
        } elseif (function_exists($controller)) {
            return $controller;
        }
    }

    $callable = $this->createController($controller);

    if (!is_callable($callable)) {
        throw new \InvalidArgumentException(sprintf('The controller for URI "%s" is not callable. %s', $request->getPathInfo(), $this->getControllerError($callable)));
    }

    return $callable;
}

以上代碼的核心片段就是$controller = $request->attributes->get('_controller')

2.3 對控制器要執行的方法進行反射 獲取方法中需要的參數 並通過容器來獲取實例化的對象返回

控制器方法中的參數處理核心代碼如下

# Symfony\Component\HttpKernel\Controller\ArgumentResolver
public function getArguments(Request $request, $controller)
{
    $arguments = array();
    //反射獲得的控制器方法中的參數,且將參數封裝到對象 Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata
    foreach ($this->argumentMetadataFactory->createArgumentMetadata($controller) as $metadata) {
        //將argument resolver 中的對象逐一進行嘗試
        foreach ($this->argumentValueResolvers as $resolver) {
            if (!$resolver->supports($request, $metadata)) {
                continue;
            }

            $resolved = $resolver->resolve($request, $metadata);

            if (!$resolved instanceof \Generator) {
                throw new \InvalidArgumentException(sprintf('%s::resolve() must yield at least one value.', get_class($resolver)));
            }
            //$resolver 返回的是 Generator yield
            foreach ($resolved as $append) {
                $arguments[] = $append;
            }

            // continue to the next controller argument
            continue 2; // 跳轉到上一層循環繼續
        }

        $representative = $controller;

        if (is_array($representative)) {
            $representative = sprintf('%s::%s()', get_class($representative[0]), $representative[1]);
        } elseif (is_object($representative)) {
            $representative = get_class($representative);
        }

        throw new \RuntimeException(sprintf('Controller "%s" requires that you provide a value for the "$%s" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.', $representative, $metadata->getName()));
    }

    return $arguments;
}

public static function getDefaultArgumentValueResolvers()
{
    return array(
        new RequestAttributeValueResolver(),
        new RequestValueResolver(),
        new SessionValueResolver(),
        new DefaultValueResolver(),
        new VariadicValueResolver(),
    );
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章