朱金辉 1 gadu atpakaļ
vecāks
revīzija
9b91912fc7

+ 3 - 0
demo/public/index.php

@@ -12,6 +12,9 @@ $app->setCachePath('tmp');
 //app相关配置
 $app->setAppConfigure('configure/app.ini');
 
+//设置静态文件目录
+$app->setStaticPath(['static', 'uploads']);
+
 //设置指定前缀是否使用namespace,仅限于使用框架本省的autoload
 $app->setUseNamespace('Bootstrap', false);
 //设置logger,用于错误日志收集

+ 28 - 0
src/Application.php

@@ -78,6 +78,8 @@ class Application
 
     public $viewEngine = 'smarty';
 
+    protected $completeHooker = [];
+
     public function __construct()
     {
         $this->helper = Psr4::getInstance()->loadClass('\Qii\Autoloader\Helper');
@@ -618,4 +620,30 @@ class Application
         $this->request->setDispatched(true);
         return $this;
     }
+
+    /**
+     * 运行结束后执行的方法
+     *
+     * @param callable $callable 方法
+     * @return void
+     */
+    public function setCompleteRun($callable = null) {
+        if(is_callable($callable)) {
+            $this->completeHooker[] = $callable;
+        }
+    }
+
+    /**
+     * 执行complete hooker
+     *
+     * @param object $controllerCls 当前controller类
+     * @return void
+     */
+    public function runComplete($controllerCls) {
+        if(is_array($this->completeHooker)) {
+            foreach ($this->completeHooker as $callable) {
+                $callable($controllerCls);
+            }
+        }
+    }
 }

+ 2 - 2
src/Autoloader/Factory.php

@@ -41,8 +41,8 @@ class Factory
         }
         $refClass = new \ReflectionClass($className);
         $instance = $refClass->newInstanceArgs($args);
-        if ($refClass->hasMethod('_initialize')) {
-            call_user_func_array(array($instance, '_initialize'), $args);
+        if ($refClass->hasMethod('initialization')) {
+            call_user_func_array(array($instance, 'initialization'), $args);
         }
         return Factory::$instance[$className] = $instance;
     }

+ 1 - 0
src/Base/Controller.php

@@ -292,6 +292,7 @@ abstract class Controller
     public function __destruct()
     {
         $this->afterRun();
+        \Qii::getInstance()->runComplete($this);
         if ($this->request && $this->request->isForward()) {
             $this->request->setForward(false);
             \Qii::getInstance()->dispatcher->setRequest($this->request);

+ 2 - 0
src/Base/Dispatcher.php

@@ -80,6 +80,7 @@ class Dispatcher
     public function dispatch($controller = '', $action = '')
     {
         $args = func_get_args();
+
         $controller = $controller != '' ? $controller : $this->request->getControllerName();
         $action = $action != '' ? $action : $this->request->getActionName();
 
@@ -97,6 +98,7 @@ class Dispatcher
         $psr4 = Psr4::getInstance();
         $this->controllerCls = call_user_func_array(array($psr4, 'loadClass'), $funcArgs);
         //load beforeRun 不放到具体的Controller里边执行,要不这里的属性controllerCls获取不到值
+        //step beforeRun -> middleware -> initialization -> run
         if(method_exists($this->controllerCls, 'beforeRun') && is_callable(array($this->controllerCls, 'beforeRun'))) {
             !$this->controllerCls->beforeRun() && exit();
         }

+ 1 - 1
src/Base/Request.php

@@ -690,7 +690,7 @@ abstract class Request
      */
     public function getExtension() {
         $extension = pathinfo($this->uri, PATHINFO_EXTENSION);
-        return $extension?? "html";
+        return $extension != "" ? $extension : "html";
     }
 
     /**

+ 45 - 11
src/Base/Response.php

@@ -42,6 +42,33 @@ class Response
      */
     private $format;
 
+    protected $_responseCode = 200;
+
+    /**
+     * Set HTTP response code to use with headers
+     *
+     * @param int $code
+     * @return Qii\Response\Http
+     */
+    public function setResponseCode($code)
+    {
+        if (!is_int($code) || (100 > $code) || (599 < $code)) {
+            throw new \Qii\Exceptions\Response('Invalid HTTP response code');
+        }
+
+        $this->_responseCode = $code;
+        return $this;
+    }
+
+    /**
+     * Retrieve HTTP response code
+     *
+     * @return int
+     */
+    public function getResponseCode()
+    {
+        return $this->_responseCode;
+    }
 
     public function __construct($data = array(), $headers = array())
     {
@@ -169,7 +196,6 @@ class Response
         }
         return false;
     }
-    
     /**
      * Send the response, including all headers
      *
@@ -177,15 +203,21 @@ class Response
      */
     public function response()
     {
+        $code = $this->getResponseCode();
+        $headerIsSent = false;
         if ($this->data && isset($this->data['body'])) {
             switch ($this->data['format']) {
                 case self::FORMAT_JSON:
+                    $this->setHeader('Status', $code);
                     $this->setHeader('Content-Type', 'text/json');
                     $this->sendHeaders();
-                    $this->_sendHeader = true;
+                    $headerIsSent = true;
                     echo json_encode($this->data['body'], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE);
                     break;
                 default:
+                    $this->setHeader('Status', $code);
+                    $this->sendHeaders();
+                    $headerIsSent = true;
                     $body = $this->data['body'];
                     if (is_array($this->data['body'])) {
                         $body = '';
@@ -199,17 +231,16 @@ class Response
                             $body = Response::$render->fetch($this->data['body']['tpl']);
                         }
                     }
-                    $this->setBody($body);
+                    //$this->setBody($body);
                     echo(IS_CLI ? (new Cli())->stdout($body) : $body);
                     break;
             }
-            return;
         }
-        if ($this->_sendHeader) {
+        if(!$headerIsSent) {
             $this->sendHeaders();
         }
         foreach ($this->body as $body) {
-            echo IS_CLI ? new Cli($body) : $body;
+            echo IS_CLI ? (new Cli())->stdout($body) : $body;
         }
     }
 
@@ -333,11 +364,14 @@ class Response
      */
     protected function sendHeaders()
     {
-        foreach ($this->headers as $key => $header) {
-            header(
-                $header['name'] . ': ' . $header['value'],
-                $header['replace']
-            );
+        //如果设置为不发送头信息,这里就不发
+        if($this->_sendHeader) {
+            foreach ($this->headers as $key => $header) {
+                header(
+                    $header['name'] . ': ' . $header['value'],
+                    $header['replace']
+                );
+            }
         }
         $this->clearHeaders();
         return $this;

+ 152 - 30
src/Base/Route.php

@@ -1,8 +1,10 @@
 <?php
 namespace Qii\Base;
 
+use Qii\Autoloader\Psr4;
 use Qii\Config\Consts;
 use Qii\Config\Register;
+use Qii\Exceptions\Variable;
 use Qii\Request\Http;
 
 /**
@@ -32,6 +34,11 @@ class Route
      */
     private $stored = [];
     /**
+     * 存储默认 prefix
+     * @var array $default
+     */
+    private $default = [];
+    /**
      * 是否启用正则匹配
      *
      * @var bool $enableExpress 默认为启用
@@ -173,6 +180,7 @@ class Route
         $uri = $request->getRequestUri();
         $middleware = [];
         $callable = '';
+        $args = null;
         foreach ($route as $item) {
             $middleware = $item[0];
             $subRoute = $item[1];
@@ -183,8 +191,12 @@ class Route
             $subRoute[$method] = isset($subRoute[$method]) ? (array) $subRoute[$method] : array();
             $routes = array_merge($subRoute[$method], $subRoute['ANY']);
             $callable = '';
-            $args = null;
             foreach ($routes as $routeUri) {
+                //如果有设置默认,把默认规则拿出来
+                if(preg_match("/(\:default)/", $routeUri[0])) {
+                    $arr = explode(":", $routeUri[0]);
+                    $this->default[$arr[0]][$method] = [$middleware, $routeUri];
+                }
                 //启用正则匹配
                 if($this->enableExpress) {
                     list($route, $args) = $this->parse($uri, $routeUri);
@@ -192,7 +204,7 @@ class Route
                         if(is_array($args)) {
                             //将$args的值设置到get参数里
                             foreach ($args as $key => $value) {
-                                $request->set($key, $value);
+                                $request->set($key, urldecode($value));
                             }
                         }
                         $callable = $route[1];
@@ -209,19 +221,20 @@ class Route
             }
         }
         if (!$callable || count($middleware) == 0) {
-            return $next($request);
+            //如果未命中规则,看看是否有默认规则
+            if(count($this->default) > 0) {
+                foreach ($this->default as $path => $value) {
+                    if(substr($uri, 0, strlen($path)) == $path && isset($value[$method])) {
+                        $middleware = $value[$method][0];
+                        $callable = $value[$method][1][1];
+                    }
+                }
+            }
+            if(!$callable) {
+                return $next($request);
+            }
         }
-        //命中后,执行相应程序后就不往下执行
-        $middleware = array_reverse(array_unique($middleware));
-        $result = array_reduce($middleware, function ($carry, $item){
-            return function () use ($carry, $item){
-                return _loadClass($item)->handle(\Qii::getInstance()->request, $carry);
-            };
-        }, function(){
-            return true;
-        })();
-        if (!$result) return false;
-        if (!$this->make($callable, $request, $args)){
+        if (!$this->make($callable, $middleware, $request, $args)){
             return $next($request);
         }
         return false;
@@ -236,13 +249,25 @@ class Route
      * @return null[]
      */
     public function parse($uri, $route) {
+        //去掉后缀名,不匹配后缀名
+        $ext = ".". pathinfo($uri, PATHINFO_EXTENSION);
+        if(substr($uri, -1 * strlen($ext)) == $ext) {
+            $uri = substr($uri, 0,-1 * strlen($ext));
+        }
+        $routeExt = ".". pathinfo($route[0], PATHINFO_EXTENSION);
+        if(substr($route[0], -1 * strlen($routeExt)) == $routeExt) {
+            $route[0] = substr($route[0], 0,-1 * strlen($routeExt));
+        }
+
+        //优先精确匹配
         if(preg_match("/\{.*?\}/i", $route[0])) {
             $uriArray = explode("/", $uri);
             $routeArray = explode("/", $route[0]);
-            //如果uri的长度比route的端,那就直接不匹配
-            if(count($uriArray) < count($routeArray)) {
+            //如果uri的长度比route不相等,且前缀不匹配就直接退出规则匹配
+            if(count($uriArray) != count($routeArray) && !preg_match("/(". str_replace("/", "\/", $uri).").*?/", $route[0])) {
                 return [null, null];
             }
+
             $args = array();
             $firstMatch = -1;
             foreach ($routeArray as $index => $val) {
@@ -251,57 +276,125 @@ class Route
                         $firstMatch = $index;
                     }
                     $key = str_replace(array("{", "}"), "", $val);
-                    $args[$key] = $uriArray[$index];
+                    $value = $uriArray[$index];
+                    //如果key未xx:yy则yy表示xx的值类型,会做强制转换,支持的类型为number、string、bool、float
+                    if(strpos($key, ":") > 0) {
+                        $keyArr = explode(":", $key);
+                        $key = $keyArr[0];
+                        $value =  $this->convertValueTo($uriArray[$index], $keyArr[1]);
+                    }
+                    $args[$key] = $value;
                 }
             }
             //首次变量的位置之前的内容不匹配,页直接返回不匹配
-            $uriMatch = join("/", array_slice($uriArray, 0, 4));
-            $routeMatch = join("/", array_slice($routeArray, 0, 4));
+            $uriMatch = join("/", array_slice($uriArray, 0, $firstMatch));
+            $routeMatch = join("/", array_slice($routeArray, 0, $firstMatch));
             if($uriMatch != $routeMatch) {
                 return [null, null];
             }
             return [$route, $args];
         }
+
+        if($route[0] == $uri . "*") {
+            return [$route, null];
+        }
+        //匹配number
+        if(preg_match("/(:number)$/i", $route[0])) {
+            $exp = str_replace("/", "\\/",  preg_replace("/(\:number)$/i", "", $route[0]));
+            $exp = "/(".$exp.")\d+?$/";
+            preg_match($exp, $uri, $match);
+            if($match) {
+                return [$route, null];
+            }
+        }
+
+        //匹配任何字符
+        if(preg_match("/(\:any|\*)$/i", $route[0])) {
+            $exp = "(". preg_replace("/(\:any|\*)$/i", "", $route[0]) .")";
+            if(preg_match($exp, $uri)) {
+                return [$route, null];
+            }
+        }
         return [null, null];
     }
+
+    /**
+     * 执行middleware
+     *
+     * @param array $middleware
+     * @return mixed|true
+     */
+    protected function handleMiddleware($middleware) {
+        //命中后,执行相应程序后就不往下执行
+        $middleware = array_reverse(array_unique((array) $middleware));
+        //获取全局middleware
+        return array_reduce($middleware, function ($carry, $item){
+            return function () use ($carry, $item){
+                return _loadClass($item)->handle(\Qii::getInstance()->request, $carry);
+            };
+        }, function(){
+            return true;
+        })();
+    }
+
     /**
      * 执行 $callable
+     *
      * @param mix $callable 执行方法
+     * @param array $middleware 中间件
      * @param HTTP $request request
      * @param array | null $args 正则匹配后拿到的参数及值
      * @return bool
+     * @throws Variable
      */
-    public function make($callable, $request, $args = null) {
+    public function make($callable, $middleware, $request, $args = null) {
         if($args === null) {
             $args = array($request);
         }
         if(is_array($args)) {
             array_unshift($args, $request);
         }
+        $res = null;
         switch ($callable) {
             case ($callable instanceof  \Closure):
-                $res = call_user_func_array($callable, $args);
-                if($res instanceof \Qii\Base\Response) {
-                    return $res->response();
+                if(count($middleware) > 0 && !$this->handleMiddleware($middleware)) {
+                    return true;
                 }
-                echo $res;
+                $res = call_user_func_array($callable, $args);
                 break;
             case is_array($callable):
                 $action = (isset($callable[1]) ? $callable[1] : "index"). Register::get(Consts::APP_DEFAULT_ACTION_SUFFIX);
-                $res = call_user_func_array(array(_loadClass($callable[0]), $action), $args);
-                if($res instanceof \Qii\Base\Response) {
-                    return $res->response();
+                $controllerCls = _loadClass($callable[0]);
+                if(method_exists($controllerCls, 'beforeRun') && !$controllerCls->beforeRun()) {
+                    return true;
+                }
+
+                if(count($middleware) > 0 && !$this->handleMiddleware($middleware)) {
+                    return true;
                 }
+                if(method_exists($controllerCls, 'initView')) {
+                    $controllerCls->initView();
+                }
+                if(method_exists($controllerCls, 'initialization')) {
+                    $controllerCls->initialization();
+                }
+                $res = call_user_func_array(array($controllerCls, $action), $args);
                 break;
             case gettype($callable) == 'string':
+                if(count($middleware) > 0 && !$this->handleMiddleware($middleware)) {
+                    return true;
+                }
                 $res = call_user_func_array(
                         array(_loadClass($callable),
                             Register::get("APP_DEFAULT_ACTION").  Register::get(Consts::APP_DEFAULT_ACTION_SUFFIX)), $args);
-                if($res instanceof \Qii\Base\Response) {
-                    return $res->response();
-                }
+
                 break;
         }
+        if($res instanceof Response) {
+            echo $res->response();
+        }else if($res !== null){
+            echo $res;
+        }
         if ($callable) {
             return true;
         }
@@ -309,6 +402,35 @@ class Route
     }
 
     /**
+     * 将值转换为指定类型
+     *
+     * @param $value
+     * @param $to
+     * @return bool|mixed|string
+     */
+    protected function convertValueTo($value, $to) {
+        if(!in_array($to, ['number', 'string', 'bool'])) {
+            return $value;
+        }
+        switch ($to) {
+            case 'number':
+                //取字符串中连续数字
+                preg_match('/(\d+)/',$value,$r);
+                if($r) {
+                    return $r[0];
+                }
+                return 0;
+            case 'string':
+                return (string) $value;
+            case 'bool':
+                if(in_array($value, ['true', 'false'])) {
+                    return $value == 'true';
+                }
+                return (bool) $value;
+        }
+        return "";
+    }
+    /**
      * 获取所有设置的路由
      *
      * @return mixed

+ 1 - 1
src/Driver/Pdo/Driver.php

@@ -117,7 +117,7 @@ class Driver extends Base implements Intf
         }
         $this->sql = $sql;
         $this->db['CURRENT'] = $this->connection->getConnectionBySQL($sql);
-        $rs = $this->db['CURRENT']->query($sql);
+        $rs = @$this->db['CURRENT']->query($sql);
         $this->setError();
         if (!$rs) {
             $error = $this->getError('error');

+ 5 - 0
src/Exceptions/Errors.php

@@ -2,6 +2,7 @@
 
 namespace Qii\Exceptions;
 
+use Qii\Autoloader\Factory;
 use Qii\Autoloader\Import;
 use Qii\Autoloader\Psr4;
 use Qii\Config\Consts;
@@ -49,6 +50,10 @@ class Errors extends \Exception
      */
     public static function getError($e)
     {
+        //设置状态码
+        $code = !in_array($e->getCode(), array(404, 400, 200)) ? 500 : $e->getCode();
+        Factory::getInstance('\Qii\Response\Http')->setResponseCode($code);
+
         $message = array();
         if (\Qii::getInstance()->request == null) {
             \Qii::getInstance()->request = \Qii::getInstance()->getRequest();

+ 0 - 28
src/Response/Http.php

@@ -5,34 +5,6 @@ use Qii\Exceptions\Response;
 
 class Http extends \Qii\Base\Response
 {
-    protected $_sendHeader = true;
-    protected $_responseCode = 200;
-
-    /**
-     * Set HTTP response code to use with headers
-     *
-     * @param int $code
-     * @return Qii\Response\Http
-     */
-    public function setResponseCode($code)
-    {
-        if (!is_int($code) || (100 > $code) || (599 < $code)) {
-            throw new Response('Invalid HTTP response code');
-        }
-
-        $this->_responseCode = $code;
-        return $this;
-    }
-
-    /**
-     * Retrieve HTTP response code
-     *
-     * @return int
-     */
-    public function getResponseCode()
-    {
-        return $this->_responseCode;
-    }
 
     /**
      * 判断是否可以输出header