云海的博客
首页
  • 接口
  • 数组
  • slice
  • map
  • 指针
  • 反射
  • Context
  • sync.map
  • 锁
  • 类型和类型指针分析
  • recover
  • 从零实现RPC框架
  • make和new区别
  • channel
  • sync.Once
  • sync.Pool
  • protobuf
  • MongoDB pkg源码-findone
  • MyBatis
  • Maven
  • 解析Laravel框架—路由处理
  • PHP(客户端)与 Golang(服务端)使用grpc+protobuf 通信
  • JAVA(客户端)与 Golang(服务端) 使用grpc+protobuf通信
  • Docker使用笔记-常用命令
  • Docker使用笔记-容器间通讯
  • Docker使用笔记-搭建Redis集群
  • Docker使用笔记-镜像多阶段构建
  • Kubernetes部署golang服务
  • Linux常用命令
  • Docker安装Prometheus与Grafana
  • Protobuf
  • TCP抓包
  • 概述-《TCP/IP详解》读书笔记
  • 索引
  • 事务隔离级别
  • 常识
  • 每日一题(1)
  • 每日一题(2)
  • 每日一题(3)
  • 每日一题(4)
关于
GitHub (opens new window)

云海

服务端研发
首页
  • 接口
  • 数组
  • slice
  • map
  • 指针
  • 反射
  • Context
  • sync.map
  • 锁
  • 类型和类型指针分析
  • recover
  • 从零实现RPC框架
  • make和new区别
  • channel
  • sync.Once
  • sync.Pool
  • protobuf
  • MongoDB pkg源码-findone
  • MyBatis
  • Maven
  • 解析Laravel框架—路由处理
  • PHP(客户端)与 Golang(服务端)使用grpc+protobuf 通信
  • JAVA(客户端)与 Golang(服务端) 使用grpc+protobuf通信
  • Docker使用笔记-常用命令
  • Docker使用笔记-容器间通讯
  • Docker使用笔记-搭建Redis集群
  • Docker使用笔记-镜像多阶段构建
  • Kubernetes部署golang服务
  • Linux常用命令
  • Docker安装Prometheus与Grafana
  • Protobuf
  • TCP抓包
  • 概述-《TCP/IP详解》读书笔记
  • 索引
  • 事务隔离级别
  • 常识
  • 每日一题(1)
  • 每日一题(2)
  • 每日一题(3)
  • 每日一题(4)
关于
GitHub (opens new window)
  • 解析Laravel框架—路由处理
    • UE5 m1编译
    • 其它
    云海
    2017-03-29
    目录

    解析Laravel框架—路由处理

    # 解析Laravel框架—路由处理

    # 介绍

    我很好奇,Laravel框架是如何解析路由(routes/web.php)文件。为什么,如下代码,就可以执行ExampleController控制器中的add方法。于是乎,我就去一层层查看框架代码,揭下它神秘的面纱。

    注:因为框架文件代码量过大,本文所贴代码,皆已删减。

    $router->get('/', 'ExampleController@add');
    
    1

    # 准备

    • Lumen(精简版的Laravel框架),选择的版本是Lumen8.2.3。
    • 好用的ide工具,选择的是PhpStorm。
    • 本地PHP版本,选择的是7.2.30。

    # 正文

    查看入口文件index.php,该文件位于public文件夹下。我们先分析第一行,require部分。

    <?php
    
    $app = require __DIR__.'/../bootstrap/app.php';
    
    $app->run();
    
    
    1
    2
    3
    4
    5
    6

    一.Require部分

    require主要就是加载bootstrap文件夹下的app.php文件。

    2.我把app.php文件中的代码贴到了下面。为了便于读者浏览,我在代码中写入了注释。

    <?php
    
    
    // 加载vendor文件夹中的扩展包。
    require_once __DIR__.'/../vendor/autoload.php';
    
    // 加载env变量
    (new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
        dirname(__DIR__)
    ))->bootstrap();
    
    // 设置默认的时区
    date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));
    
    /*
    |--------------------------------------------------------------------------
    | Create The Application
    |--------------------------------------------------------------------------
    |
    | Here we will load the environment and create the application instance
    | that serves as the central piece of this framework. We'll use this
    | application as an "IoC" container and router for this framework.
    | 
    | 在这里,我们将加载环境变量(env文件)并创建应用程序实例,作为这个框架的中心。 
    | 
    */
    
    
    // Application代码
    $app = new Laravel\Lumen\Application(
        dirname(__DIR__)
    );
    
    
    /*
    |--------------------------------------------------------------------------
    | Load The Application Routes
    |--------------------------------------------------------------------------
    |
    | Next we will include the routes file so that they can all be added to
    | the application. This will provide all of the URLs the application
    | can respond to, as well as the controllers that may handle them.
    |
    | 我们将加载路由文件。这包括所有的请求地址,看看我们如何更好的处理它们。
    */
    
    // 在本文件的上半部分,我们已创建一个app实例。这里就是将实例与路由文件绑定到一起。
    $app->router->group([
        'namespace' => 'App\Http\Controllers',
    ], function ($router) {
        require __DIR__.'/../routes/web.php';
    });
    
    return $app;
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55

    3.我们可以看到,$app实例,是由 new Application产生。然后 $app调用router属性的group方法,由此将路由文件,加载到应用中。查看下Application类。

    通过Application类文件,我们可以看到构造函数中调用了bootstrapRouter方法。此方法中,又包含了对router属性的赋值。

    文件位置:vendor/laravel/lumen-framework/src/Application.php

    <?php
    
    namespace Laravel\Lumen;
    
    use Illuminate\Container\Container;
    use Laravel\Lumen\Routing\Router;
    
    class Application extends Container
    {
        // 加载RoutesRequests trait文件,该文件是用于处理路由。着重讲解。
        use Concerns\RoutesRequests;
    
        /**
         * The Router instance.
         *
         * @var \Laravel\Lumen\Routing\Router
         */
        public $router;
        
        /**
         * Create a new Lumen application instance.
         * 创建app实例,如下构造函数。
         *  
         * @param  string|null  $basePath
         * @return void
         */
        public function __construct($basePath = null)
        {
            // 路由部分。
            $this->bootstrapRouter();
        }
    
        /**
         * Bootstrap the router instance.
         * 为router属性赋值。
         *
         * @return void
         */
        public function bootstrapRouter()
        {
            $this->router = new Router($this);
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44

    4.接下来看看,Router类中的group方法,又起到了什么作用。 Router类文件位置:vendor/laravel/lumen-framework/src/Routing/Router.php

    <?php
    
    namespace Laravel\Lumen\Routing;
    
    use Illuminate\Support\Arr;
    
    class Router
    {
        /**
         * The application instance.
         *
         * @var \Laravel\Lumen\Application
         */
        public $app;
    
    
        /**
         * Router constructor.
         *
         * @param  \Laravel\Lumen\Application  $app
         */
        public function __construct($app)
        {
            $this->app = $app;
        }
    
        /**
         * Register a set of routes with a set of shared attributes.
         *
         * @param  array  $attributes
         * @param  \Closure  $callback
         * @return void
         */
        public function group(array $attributes, \Closure $callback)
        {
            if (isset($attributes['middleware']) && is_string($attributes['middleware'])) {
                $attributes['middleware'] = explode('|', $attributes['middleware']);
            }
    
            $this->updateGroupStack($attributes);
    
            call_user_func($callback, $this);
    
            array_pop($this->groupStack);
        }
    
        
        /**
         * Update the group stack with the given attributes.
         * 整合传入的参数。存储到groupStack数组。
         *
         * @param  array  $attributes
         * @return void
         */
        protected function updateGroupStack(array $attributes)
        {
            if (! empty($this->groupStack)) {
                $attributes = $this->mergeWithLastGroup($attributes);
            }
    
            $this->groupStack[] = $attributes;
        }
    
        /**
         * Add a route to the collection.
         *
         * @param  array|string  $method
         * @param  string  $uri
         * @param  mixed  $action
         * @return void
         */
        public function addRoute($method, $uri, $action)
        {
            $action = $this->parseAction($action);
    
            $attributes = null;
    
            if ($this->hasGroupStack()) {
                $attributes = $this->mergeWithLastGroup([]);
            }
    
            if (isset($attributes) && is_array($attributes)) {
                if (isset($attributes['prefix'])) {
                    $uri = trim($attributes['prefix'], '/').'/'.trim($uri, '/');
                }
    
                if (isset($attributes['suffix'])) {
                    $uri = trim($uri, '/').rtrim($attributes['suffix'], '/');
                }
    
                $action = $this->mergeGroupAttributes($action, $attributes);
            }
    
            $uri = '/'.trim($uri, '/');
    
            if (isset($action['as'])) {
                $this->namedRoutes[$action['as']] = $uri;
            }
    
            if (is_array($method)) {
                foreach ($method as $verb) {
                    $this->routes[$verb.$uri] = ['method' => $verb, 'uri' => $uri, 'action' => $action];
                }
            } else {
                $this->routes[$method.$uri] = ['method' => $method, 'uri' => $uri, 'action' => $action];
            }
        }
    
        /**
         * Register a route with the application.
         *
         * @param  string  $uri
         * @param  mixed  $action
         * @return $this
         */
        public function get($uri, $action)
        {
            $this->addRoute('GET', $uri, $action);
    
            return $this;
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123

    5.我把group方法,单独拿出来讲解。这个方法,有两个入参,一个是数组,另一个是回调方法。

    public function group(array $attributes, \Closure $callback)
    {
        call_user_func($callback, $this);
    }
    
    1
    2
    3
    4

    在app.php文件中,咱们填入的参数如下:

    app.php文件。group方法的调用参数。
    
    [
        'namespace' => 'App\Http\Controllers',
    ], function ($router) {
        require __DIR__.'/../routes/web.php';
        // 实际加载后的代码,如下所示:
        // $router->get('/', 'ExampleController@add');
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    6.使用call_user_func执行回调方法,当前路由为get请求,于是调用Router类中的get方法。

    get方法中,又进行addRoute操作,addRoute就是将路由存入routes属性中。加载操作,到这里,就结束了。下一步,看看如何执行。

    public function get($uri, $action)
    {
        $this->addRoute('GET', $uri, $action);
    
        return $this;
    }
    
    1
    2
    3
    4
    5
    6
    public function addRoute($method, $uri, $action)
    {
        $uri = '/'.trim($uri, '/');
    
        if (isset($action['as'])) {
            $this->namedRoutes[$action['as']] = $uri;
        }
    
        if (is_array($method)) {
            foreach ($method as $verb) {
                $this->routes[$verb.$uri] = ['method' => $verb, 'uri' => $uri, 'action' => $action];
            }
        } else {
            $this->routes[$method.$uri] = ['method' => $method, 'uri' => $uri, 'action' => $action];
        }
    }
    
    
    // 存入后的格式
    $this->routes[$method.$uri] = 
    array(1) {
        ["GET/"]=>array(3) {
                    ["method"]=> string(3) "GET"
                    ["uri"]=> string(1) "/"
                    ["action"]=> array(1) {
                                ["uses"]=> string(42) "App\Http\Controllers\ExampleController@add"
                    }
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30

    二.Run部分

    1.接下来,分析run部分。

    $app->run();
    
    1

    $app变量是Application实例,于是去Application.php,查找run方法。最后在该文件引入的trait 类, Concerns\RoutesRequests 文件中找到。

    RoutesRequests文件位置:vendor/laravel/lumen-framework/src/Concerns/RoutesRequest.php

    public function run($request = null)
    {
        // 请求处理
        $response = $this->dispatch($request);
    
        // 返回值
        if ($response instanceof SymfonyResponse) {
            $response->send();
        } else {
            echo (string) $response;
        }
    
        if (count($this->middleware) > 0) {
            $this->callTerminableMiddleware($response);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    2.请求处理部分是在dispatch这个方法中。核心代码。 以下,就是我删减,合并后的代码。这样看起来,就清晰明了。

    首先是通过routes,验证该路由是否存在。存在,就进行下一步的处理,拆分controller与method名称。使用make实例化controller, 去验证method是否存在。

    $instance 是make实例化,核心就是make。 make里面使用了反射类。

    创建反射,之后从给出的参数创建一个新的类实例。执行[$instance, 'add'] () 就可以调用ExampleController中的add方法。这样就达到了,执行控制器方法的效果。

    $reflector = new ReflectionClass($concrete);

    $reflector->newInstanceArgs($instances);

        public function dispatch($request = null)
        {
            // 丢到一个通道去执行操作
            return $this->sendThroughPipeline($this->middleware, function ($request) use ($method, $pathInfo) {
                $this->instance(Request::class, $request);
    
                // 判断路由是否存在,存在继续执行
                if (isset($this->router->getRoutes()[$method.$pathInfo])) {
                      return $this->prepareResponse($this->callControllerAction($routeInfo));
                }
            });
            
        }
        
        // 处理路由,将路由中的controller与method 拆开
        protected function callControllerAction($routeInfo)
        {
            $uses = $routeInfo[1]['uses'];
    
            if (is_string($uses) && ! Str::contains($uses, '@')) {
                $uses .= '@__invoke';
            }
    
            [$controller, $method] = explode('@', $uses);
    
            // $instance赋值
            if (! method_exists($instance = $this->make($controller), $method)) {
                throw new NotFoundHttpException;
            }
    
            return $this->callControllerCallable($instance, $method, $routeInfo);
            
        }
        
        
        protected function callControllerCallable($instance ,$method, $routeInfo)
        {
            return $this->prepareResponse(
                $this->call([$instance, $method], $routeInfo[2])
            );
        }
        
        public function call($callback, array $parameters = [], $defaultMethod = null)
        {
            return $callback();
        }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46

    3.如下,就是针对路由,产生的反射类及类实例。

    $router->get('/', 'ExampleController@add');
    
    1
    $reflection = new \ReflectionClass('ExampleController');
    $instance = $reflection->newInstanceArgs();
    [$instance, 'add']()
    
    1
    2
    3

    # 总结

    Laravel 框架很多地方都是用到了反射机制,这篇文章只是分析了一小部分。多分析框架代码,还是有好处的。

    上次更新: 2023/10/07
    UE5 m1编译

    UE5 m1编译→

    最近更新
    01
    函数
    04-11
    02
    面试题
    04-11
    03
    EFK日志收集系统单机版
    08-18
    更多文章>
    Theme by Vdoing | Copyright © 2022-2025 Evan Xu | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式