webman框架源码修改及性能优化(四)

kaoson

在写中间件时,发现一个比较重要问题,那就是中间件中获取的controller对象,不是原来的controller对象,这样不严谨,也导致在__construct()中对controller修改的属性不生效,所以需要修改几个地方。

控制器中间件代码通常是这样:

if ($request->controller) {
            $controller = Container::get($request->controller); //这里获取到的不是原来的controller对象
}

下面是分析和修改流程:

  1. 查找src/Container.php中的get()方法,调试发现是new了一个新的实例
    public function get(string $name)
    {
        if (!isset($this->instances[$name])) {
            if (isset($this->definitions[$name])) {
                $this->instances[$name] = call_user_func($this->definitions[$name], $this);
            } else {
                if (!class_exists($name)) {
                    throw new NotFoundException("Class '$name' not found");
                }
                $this->instances[$name] = new $name(); //返回了一个新的实例,instances和definitions都不起作用
            }
        }
        return $this->instances[$name];
    }

    刚开始准备改这个get()方法,但这个方法对应接口仅提供一个$name参数,不想改动太大,所以换个思路改make()方法,原make()方法:

    
    public function make(string $name, array $constructor = [])
    {
        if (!class_exists($name)) {
            throw new NotFoundException("Class '$name' not found");
        }
        return new $name(... array_values($constructor));
    }
需要在new的时候,同时加入到instances属性中,修改后如下:
```php
public function make(string $name, array $constructor = [])
    {
        if (!class_exists($name)) {
            throw new NotFoundException("Class '$name' not found");
        }
        // return new $name(... array_values($constructor));
        $this->instances[$name] = new $name(... array_values($constructor));
        return $this->instances[$name];
    }
  1. 发现make()方法执行是在中间件的Container::get()之后,分析src/App.php的流程,可以对getCallback()方法进行修改,多传递一个request参数,然后稍微调整,就可以让make()方法先执行,修改后代码如下:

    protected static function getCallback(string $plugin, string $app, $call, array $args = null, bool $withGlobalMiddleware = true, RouteObject $route = null)
    {
        $args = $args === null ? null : array_values($args);
        $middlewares = [];
        if ($route) {
            $routeMiddlewares = array_reverse($route->getMiddleware());
            foreach ($routeMiddlewares as $className) {
                $middlewares[] = [$className, 'process'];
            }
        }
        $middlewares = array_merge($middlewares, Middleware::getMiddleware($plugin, $app, $withGlobalMiddleware));
    
        foreach ($middlewares as $key => $item) {
            $middleware = $item[0];
            if (is_string($middleware)) {
                $middleware = static::container($plugin)->get($middleware);
            } elseif ($middleware instanceof Closure) {
                $middleware = call_user_func($middleware, static::container($plugin));
            }
            if (!$middleware instanceof MiddlewareInterface) {
                throw new InvalidArgumentException('Not support middleware type');
            }
            $middlewares[$key][0] = $middleware;
        }
    
        $needInject = static::isNeedInject($call, $args);
        if (is_array($call) && is_string($call[0])) {
            $controllerReuse = static::config($plugin, 'app.controller_reuse', true);
            if (!$controllerReuse) {
                if ($needInject) {
                    $call = function ($request, ...$args) use ($call, $plugin) {
                        $call[0] = static::container($plugin)->make($call[0], [$request]);
                        $reflector = static::getReflector($call);
                        $args = static::resolveMethodDependencies($plugin, $request, $args, $reflector);
                        return $call(...$args);
                    };
                    $needInject = false;
                } else {
                    $call = function ($request, ...$args) use ($call, $plugin) {
                        $call[0] = static::container($plugin)->make($call[0], [$request]);
                        return $call($request, ...$args);
                    };
                }
            } else {
                $call[0] = static::container($plugin)->get($call[0]);
            }
        }
    
        if ($needInject) {
            $call = static::resolveInject($plugin, $call);
        }
    
        if ($middlewares) {
            $callback = array_reduce($middlewares, function ($carry, $pipe) {
                return function ($request) use ($carry, $pipe) {
                    try {
                        return $pipe($request, $carry);
                    } catch (Throwable $e) {
                        return static::exceptionResponse($e, $request);
                    }
                };
            }, function ($request) use ($call, $args) {
                try {
                    if ($args === null) {
                        $response = $call($request);
                    } else {
                        $response = $call($request, ...$args);
                    }
                } catch (Throwable $e) {
                    return static::exceptionResponse($e, $request);
                }
                if (!$response instanceof Response) {
                    if (!is_string($response)) {
                        $response = static::stringify($response);
                    }
                    $response = new Response(200, [], $response);
                }
                return $response;
            });
        } else {
            if ($args === null) {
                $callback = $call;
            } else {
                $callback = function ($request) use ($call, $args) {
                    return $call($request, ...$args);
                };
            }
        }
        return $callback;
    }

    改动对比图:
    截图

截图

  1. 对调用getCallback()的两个地方,加上request参数
    165 行
    $callback = static::getCallback($plugin, $app, [$controller, $action]);

    改为:

    $callback = static::getCallback($plugin, $app, [$controller, $action], $args = null, $withGlobalMiddleware = true, $route = null, $request);

大概557行

$callback = static::getCallback($plugin, $app, $callback, $args, true, $route);

改为

$callback = static::getCallback($plugin, $app, $callback, $args, true, $route, $request);

好了,重新调试,OK!

1327 5 1
5个评论

PHP甩JAVA一条街

好厉害

  • wash2018 2023-06-08

    这次我站队官方,我也觉得前半个洋葱不该获取到控制器实例

  • wash2018 2023-06-08

    kaoson是个大佬

walkor

这样有两个问题
1、内存始终会缓存一些控制器实例,这会导致这些控制器以及控制器引用的对象不释放,比如__destruct(){}无法及时执行
2、webman容器可配置,如果改成其它容器,那么你这个做法可能就失效了

  • kaoson 2023-06-07

    复用模式下本身就不用释放的,非复用模式下,是不是可以在中间件执行完毕后释放?这样稍改下代码就行,当然,还有没有更好的方式?
    开发者要换自己的容器,那应该得理解框架运行流程,当然相关地方要做好处理。

  • walkor 2023-06-07

    容器一般是标准的容器,比如 php-di,make方法时它们不会保存实例

  • kaoson 2023-06-07

    think、laravel貌似都是这么干的吧,think基本抄的laravel,如果你认为这样不行,那怎么解决我提的问题?

  • walkor 2023-06-07

    如果要获取同一个控制器实例,需要开启控制器复用

  • kaoson 2023-06-07

    非复用下,每个中间件都要去new一下,不科学啊

  • walkor 2023-06-07

    根据中间件洋葱模型,前置中间件就不应该得到控制器实例,控制器实例应该是在达到洋葱芯才能实例化,提前实例化才是不科学的。

  • kaoson 2023-06-07

    你这么说有道理,让我又重新思索之前的做法,原来我就是认为beforeAction, afterAction本来就属于控制器内部的东西,干嘛要交给中间件去调用?感觉比较矛盾。

  • walkor 2023-06-07

    如果你想改webman的http内核,可以用自定义进程去做
    https://www.workerman.net/doc/webman/process.html

  • kaoson 2023-06-07

    好的,回头研究一下

  • wocall 2023-06-10

    实在是佩服,虽然看不懂

  • kangyu 2023-07-11

    大佬之间的交流

缝合

囧,看了半天代码,配置上有这个控制器复用的参数。开启复用的情况下,controller是不会重新new的。截图
我还以为不支持

  • 暂无评论
W

大佬们啊!请接收下我的膝盖吧

  • 暂无评论
PHP甩JAVA一条街

你是fastadmin 作者吗

kaoson

520
积分
0
获赞数
0
粉丝数
2023-05-30 加入
🔝