在寫中間件時(shí),發(fā)現(xiàn)一個(gè)比較重要問(wèn)題,那就是中間件中獲取的controller對(duì)象,不是原來(lái)的controller對(duì)象,這樣不嚴(yán)謹(jǐn),也導(dǎo)致在__construct()中對(duì)controller修改的屬性不生效,所以需要修改幾個(gè)地方。
控制器中間件代碼通常是這樣:
if ($request->controller) {
$controller = Container::get($request->controller); //這里獲取到的不是原來(lái)的controller對(duì)象
}
下面是分析和修改流程:
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(); //返回了一個(gè)新的實(shí)例,instances和definitions都不起作用
}
}
return $this->instances[$name];
}
剛開始準(zhǔn)備改這個(gè)get()方法,但這個(gè)方法對(duì)應(yīng)接口僅提供一個(gè)$name參數(shù),不想改動(dòng)太大,所以換個(gè)思路改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的時(shí)候,同時(shí)加入到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];
}
發(fā)現(xiàn)make()方法執(zhí)行是在中間件的Container::get()之后,分析src/App.php的流程,可以對(duì)getCallback()方法進(jìn)行修改,多傳遞一個(gè)request參數(shù),然后稍微調(diào)整,就可以讓make()方法先執(zhí)行,修改后代碼如下:
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;
}
改動(dòng)對(duì)比圖:
$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);
好了,重新調(diào)試,OK!
這樣有兩個(gè)問(wèn)題
1、內(nèi)存始終會(huì)緩存一些控制器實(shí)例,這會(huì)導(dǎo)致這些控制器以及控制器引用的對(duì)象不釋放,比如__destruct(){}
無(wú)法及時(shí)執(zhí)行
2、webman容器可配置,如果改成其它容器,那么你這個(gè)做法可能就失效了
復(fù)用模式下本身就不用釋放的,非復(fù)用模式下,是不是可以在中間件執(zhí)行完畢后釋放?這樣稍改下代碼就行,當(dāng)然,還有沒(méi)有更好的方式?
開發(fā)者要換自己的容器,那應(yīng)該得理解框架運(yùn)行流程,當(dāng)然相關(guān)地方要做好處理。
根據(jù)中間件洋蔥模型,前置中間件就不應(yīng)該得到控制器實(shí)例,控制器實(shí)例應(yīng)該是在達(dá)到洋蔥芯才能實(shí)例化,提前實(shí)例化才是不科學(xué)的。
你這么說(shuō)有道理,讓我又重新思索之前的做法,原來(lái)我就是認(rèn)為beforeAction, afterAction本來(lái)就屬于控制器內(nèi)部的東西,干嘛要交給中間件去調(diào)用?感覺(jué)比較矛盾。
囧,看了半天代碼,配置上有這個(gè)控制器復(fù)用的參數(shù)。開啟復(fù)用的情況下,controller是不會(huì)重新new的。
我還以為不支持
你是fastadmin 作者嗎
好厲害