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

kaoson
  1. 通常,很多人都是在控制器里直接调用view()方法渲染视图文件,因为很多框架都是这么干的,于是大家都这么干了,但这种方式并不好,使得view与controller极度耦合。

  2. webman中的controller没有传递request对象,使得代码写起来有些繁琐,基本每个action要传递request,但如果在construct中传递request,就可以这么写:

protected $request;

public function __construct(Request $request)
{
    $this->request = $request;
}
  1. 可以利用中间件解决很多问题,但中间件无法处理控制器内部的业务逻辑,假设在调用action之前,我们希望业务逻辑通过是否有中断标识,决定是否真正调用action,用中间件搞感觉很麻烦,如果硬编码在每个action里,又觉得很繁琐,再有,我们想通过在调用action后统一处理action的返回结果,决定是否调用view渲染视图还是返回json等等。

修改后的代码:

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]);

                        if (true !== ($result = static::beforeCall($call[0], $call[1], $request))) {
                            return $result;
                        }

                        $reflector = static::getReflector($call);
                        $args = static::resolveMethodDependencies($plugin, $request, $args, $reflector);
                        // return $call(...$args);
                        return static::afterCall($call[0], $call[1], $call(...$args), $request);
                    };
                    $needInject = false;
                } else {
                    $call = function ($request, ...$args) use ($call, $plugin) {
                        $call[0] = static::container($plugin)->make($call[0], [$request]);

                        if (true !== ($result = static::beforeCall($call[0], $call[1], $request))) {
                            return $result;
                        }

                        // return $call($request, ...$args);
                        return static::afterCall($call[0], $call[1], $call($request, ...$args), $request);
                    };
                }
            } 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;
    }

    protected static function beforeCall($controller, $action, $request)
    {
        if (false !== ($result = static::detectHalted($controller))) {
            return $result;
        }

        static::doBeforeAction($controller, $action, $request);

        if (false !== ($result = static::detectHalted($controller))) {
            return $result;
        }

        return true;
    }

    protected static function afterCall($controller, $action, $response, $request)
    {
        $method = 'after' . ucfirst($action) . 'Action';
        if (method_exists($controller, $method)) {
            $response = $controller->{$method}($response, $request);
        }

        if (method_exists($controller, 'afterAction')) {
            $response = $controller->afterAction($response, $request);
        }

        return $response;
    }

    protected static function detectHalted($controller)
    {
        if (method_exists($controller, 'halted')  && $controller->halted()) {
            return $controller->getHaltedResult();
        }

        return false;
    }

    protected static function doBeforeAction($controller, $action, $request)
    {
        if (method_exists($controller, 'beforeAction')) {
            $controller->beforeAction($request);
        }

        $method = 'before' . ucfirst($action) . 'Action';
        if (method_exists($controller, $method)) {
            $controller->{$method}($request);
        }
    }

食用示例:

namespace app\controller;

use Webman\App;
use support\Model;
use support\Request;
use support\Response;

/**
 * 基础控制器
 */
class Base
{
    protected $halted = false; //标识是否中断
    protected $haltedResult = null; //接收中断返回的内容
    protected $request = null;

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function halted($flag = null)
    {
        if (is_bool($flag)) {
            $this->halted = $flag;
            return;
        }

        return $this->halted;
    }

    public function getHaltedResult()
    {
        return $this->haltedResult;
    }

    // 统一处理action返回的结果
    public function afterAction($data)
    {
        $script = $this->request->script;
        if (!$data) {
            return view($script, $data);
        }

        return $data;
    }

}
1322 4 0
4个评论

walkor

我说下我看法,不一定对。

1、把view() json() xml() redirect()等函数独立出来我觉得是比较解耦的做法,可以在控制器里使用,也可以在非控制器例如队列里发送邮件时需要调用模版时使用。如果你的意思是要把这些函放在控制器基类里,那么当你使用这些函数时就必须继承基类并且初始化一个控制器才能使用。
当然如果喜欢这么用直接自己创建一个基类就可以了。

2、关于使用$this->request还是传递$request,我觉得每次使用request都要打一遍$this->也很繁琐。如果不喜欢使用参数$request可以使用函数request()也可以省略参数少打很多字。
还有如果控制器里存储$request这种状态数据就无法复用控制器实例,开启控制器复用性能会有一定的提升,框架为了兼容两种方式不能将$request强制存储在控制器属性里。
当然如果喜欢这样用就可以像类似你说的写一个基类将$request存储在控制属性里。

3、中间件做的就是拦截请求和响应的,所以你说的beforeAction和afterAction可以用中间件直接来做。
参考 https://www.workerman.net/plugin/30

你上面说的这些都可以很方便的在webman上改造,但是webman框架不能强制做这些。

  • kaoson 2023-05-30
    1. 在控制器里直接view() json() xml() redirect()都是耦合的,我一直用自己的框架(没有发布,因为没时间搞文档),在框架建构上,我有独自看法,action返回array或一个赋值器(创建变量的arrayobject对象),交给一个中间件去处理,是否应该返回view、json或者xml,根据客户端传递的参数确定,比如returntype=json,那么就返回json,默认返回view,这样,就不用重复去写代码。

    基类对很多人来说都是有必要的,我相信大多数人都会写基类,我提供的这个基类只是一小部分。另外,可能还会需要两个以上基类。

    1. 在__construct()中传递request是为了代码封装,有时候把一部分业务逻辑放__construct()中处理比较方便,当然,我也认为$this->这样反复写也很累,只是建议多提供一种方式。

    2. 中间件能解决类似装饰器模式的问题,但有时也很繁琐,如果业务逻辑比较复杂,可能需要写一堆中间件,其实如果能交给控制器本身处理,反而简单明了。另外,中间件很难处理控制器里的业务逻辑,因为中间件只是简单的before和after,但如果在控制器里,就可以方便的控制了。

  • walkor 2023-05-31

    1、webman也可以像你说的这样干,但是webman不能强制这样做
    2、写个基类构造函数就解决了,但是为了控制器复用,webman不能强制这样做
    3、中间件可以重写整个控制器处理逻辑,不仅仅简单的before after

  • kaoson 2023-05-31

    客户端提交数据时,后端如何验证数据?是放中间件?那不是要写很多中间件?在我自己的框架中,我通常是放beforeAction(全局)或beforeXxxAction(某个),验证数据放控制器里简单明了,中间件我只用于通用性比较强的业务逻辑。

  • walkor 2023-05-31

    之前写过beforeAction afterAction的插件,beforeAction afterAction是定义在控制器中的,用中间件实现的。
    这个不好在内核实现,影响性能
    https://www.workerman.net/plugin/30

  • uspear 2023-06-09

    客户端提交数据时,后端如何验证数据?自建个validate文件夹在里面处理,walkor大佬可以整个像fastapi框架那要快速就好了。用习惯了java每次请求接口类型都要自己判断很不爽……

PHP甩JAVA一条街

两大佬好厉害, 看不懂说啥

  • wash2018 2023-06-08

    理念上的碰撞,每种方式都用拥护者,

kaoson

经过再三考虑,仅非复用模式下对__construct()方法加上request参数,before,after相关方法移入中间件实现,但移植后发现问题,原来是框架强制返回了Response对象,这个还好处理,还有一个比较重要问题,那就是中间件的controller不是原来的controller,是重新new了一个,查了源码,应该还需要修改Container类,另起一片文章讨论。

  • 暂无评论
1

cool!

  • 暂无评论

kaoson

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