在写中间件时,发现一个比较重要问题,那就是中间件中获取的controller对象,不是原来的controller对象,这样不严谨,也导致在__construct()中对controller修改的属性不生效,所以需要修改几个地方。
控制器中间件代码通常是这样:
if ($request->controller) {
$controller = Container::get($request->controller); //这里获取到的不是原来的controller对象
}
下面是分析和修改流程:
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];
}
发现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;
}
改动对比图:
$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!
好厉害
这次我站队官方,我也觉得前半个洋葱不该获取到控制器实例
kaoson是个大佬
这样有两个问题
1、内存始终会缓存一些控制器实例,这会导致这些控制器以及控制器引用的对象不释放,比如
__destruct(){}
无法及时执行2、webman容器可配置,如果改成其它容器,那么你这个做法可能就失效了
复用模式下本身就不用释放的,非复用模式下,是不是可以在中间件执行完毕后释放?这样稍改下代码就行,当然,还有没有更好的方式?
开发者要换自己的容器,那应该得理解框架运行流程,当然相关地方要做好处理。
容器一般是标准的容器,比如 php-di,make方法时它们不会保存实例
think、laravel貌似都是这么干的吧,think基本抄的laravel,如果你认为这样不行,那怎么解决我提的问题?
如果要获取同一个控制器实例,需要开启控制器复用
非复用下,每个中间件都要去new一下,不科学啊
根据中间件洋葱模型,前置中间件就不应该得到控制器实例,控制器实例应该是在达到洋葱芯才能实例化,提前实例化才是不科学的。
你这么说有道理,让我又重新思索之前的做法,原来我就是认为beforeAction, afterAction本来就属于控制器内部的东西,干嘛要交给中间件去调用?感觉比较矛盾。
如果你想改webman的http内核,可以用自定义进程去做
https://www.workerman.net/doc/webman/process.html
好的,回头研究一下
实在是佩服,虽然看不懂
大佬之间的交流
囧,看了半天代码,配置上有这个控制器复用的参数。开启复用的情况下,controller是不会重新new的。
我还以为不支持
大佬们啊!请接收下我的膝盖吧
你是fastadmin 作者吗
同问
Karson/FastAdmin
kaoson
不一样吧