| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237 | <?php
namespace Luticate\Utils\Controller;
use FastRoute\Dispatcher;
use Luticate\Utils\Business\LuBusiness;
use Luticate\Utils\Business\LuBusinessException;
use Luticate\Utils\Business\LuDocParser;
use Luticate\Utils\Dbo\LuDboConstraintException;
use Luticate\Utils\Dbo\LuDboDeserializeException;
use FastRoute\DataGenerator\GroupCountBased as DataGeneratorGroupCountBased;
use FastRoute\Dispatcher\GroupCountBased as DispatcherGroupCountBased;
use FastRoute\RouteCollector;
use FastRoute\RouteParser\Std;
use Luticate\Utils\Dbo\LuRouteDbo;
use Luticate\Utils\LuAbstractMiddleware;
class LuRoute {
    const REG_UINT = "[0-9]+";
    const REG_INT = "\\-?" . self::REG_UINT;
    const REG_BOOL = "true|false";
    /**
     * @var DispatcherGroupCountBased
     */
    private $_dispatcher = null;
    /**
     * @var string[]
     */
    private $middleware = array('Luticate\Utils\ParametersMiddleware');
    /**
     * @var LuRouteDbo[]
     */
    private $routes = array();
    /**
     * @var LuRoute
     */
    private static $instance = null;
    private function __construct()
    {
    }
    public static function getInstance()
    {
        if (is_null(self::$instance)) {
            self::$instance = new static();
        }
        return self::$instance;
    }
    public function getRoutes()
    {
        return $this->routes;
    }
    /**
     * @param $middleware string
     */
    public function addMiddleware($middleware)
    {
        $this->middleware[] = $middleware;
    }
    
    public function setup()
    {
        $routeCollector = new RouteCollector(new Std(), new DataGeneratorGroupCountBased());
        foreach ($this->getRoutes() as $route) {
            $routeCollector->addRoute($route->getMethod(), $route->getUrl(), function() use($route)
            {
                $router = static::getInstance();
                return $router->execute($route);
            });
        }
        $this->_dispatcher = new DispatcherGroupCountBased($routeCollector->getData());
    }
    public function execute(LuRouteDbo $route)
    {
        $middlewares = [];
        foreach ($route->getMiddlewares() as $middlewareName) {
            /**
             * @var $middleware LuAbstractMiddleware
             */
            $middleware = new $middlewareName();
            $middleware->onBefore($route);
            $middlewares[] = $middleware;
        }
        $reflect = new \ReflectionMethod($route->getControllerClass(), $route->getControllerMethod());
        $params = $reflect->getParameters();
        $doc = new LuDocParser($reflect->getDocComment());
        $doc->parse();
        $args = array();
        foreach ($params as $param) {
            try {
                if ($param->isOptional()) {
                    $value = null;
                    if (LuBusiness::hasParam([$param->getName()])) {
                        $value = $this->getParam($param, LuBusiness::getParam($param->getName()));
                    }
                    else {
                        $value = $param->getDefaultValue();
                    }
                    $args[$param->getName()] = $value;
                }
                else {
                    $args[$param->getName()] = $this->getParam($param, LuBusiness::checkParam($param->getName()));
                }
                if (array_key_exists($param->getName(), $doc->getParams())) {
                    foreach ($doc->getParams()[$param->getName()]->getConstraints() as $constraint) {
                        call_user_func_array([$args[$param->getName()], $constraint->getMethod()],
                            $constraint->getArguments());
                    }
                }
            }
            catch (LuDboConstraintException $e)
            {
                $paramName = $param->getName();
                LuBusiness::badInput("Invalid value for '${paramName}': " . $e->getMessage());
            }
        }
        $controller = $route->getControllerClass();
        $controllerInstance = new $controller();
        $result = call_user_func_array(array($controllerInstance, $route->getControllerMethod()), $args);
        foreach ($middlewares as $middleware) {
            $middleware->onAfter($route, $result);
        }
        return $result;
    }
    public function dispatch(string $httpMethod, string $url)
    {
        $routeInfo = $this->_dispatcher->dispatch($httpMethod, $url);
        if ($routeInfo[0] == Dispatcher::NOT_FOUND) {
            throw new LuBusinessException("Route not found", 404);
        }
        else if ($routeInfo[0] == Dispatcher::METHOD_NOT_ALLOWED) {
            throw new LuBusinessException("Method not allowed", 405);
        }
        else {
            $handler = $routeInfo[1];
            $vars = $routeInfo[2];
            $handler($vars);
        }
    }
    
    private function getClassName(\ReflectionParameter $param) {
        preg_match('/\[\s\<\w+?>\s([\w]+)/s', $param->__toString(), $matches);
        return isset($matches[1]) ? $matches[1] : null;
    }
    private function getParam(\ReflectionParameter $param, $value)
    {
        $typedValue = null;
        $className = $this->getClassName($param);
        if (is_null($className)) {
            $typedValue = $value;
        }
        else {
            $class = $param->getClass();
            try
            {
                $json = json_decode($value, true);
                if (is_null($json)) {
                    $json = $value . "";
                }
                $typedValue = call_user_func_array(array($class->getName(), "jsonDeserialize"), array($json));
            }
            catch (LuDboDeserializeException $e)
            {
                throw $e;
            }
            catch (\Exception $e)
            {
                LuLog::log($e);
                LuBusiness::badInput("Unable to parse JSON value for '" . $param->getName() . "'");
            }
        }
        return $typedValue;
    }
    public function addRoute(string $httpMethod, string $url, string $controller, string $method, $permissions = array(), $middlewares = array())
    {
        if (!is_array($permissions)) {
            $permissions = array($permissions);
        }
        if (!is_array($middlewares)) {
            $middlewares = array($middlewares);
        }
        if (strpos($controller, "\\") === false) {
            $controller = "App\\Controller\\" . $controller . "Controller";
        }
        $route = new LuRouteDbo();
        $route->setUrl($url);
        $route->setControllerClass($controller);
        $route->setControllerMethod($method);
        $route->setMiddlewares($middlewares);
        $route->setPermissions($permissions);
        $route->setMethod($httpMethod);
        $this->routes[] = $route;
    }
    
    public function get(string $url, string $controller, string $method, $permissions = array(), $middleware = array())
    {
        $this->addRoute("GET", $url, $controller, $method, $permissions, $middleware);
    }
    public function post(string $url, string $controller, string $method, $permissions = array(), $middleware = array())
    {
        $this->addRoute("POST", $url, $controller, $method, $permissions, $middleware);
    }
    public function put(string $url, string $controller, string $method, $permissions = array(), $middleware = array())
    {
        $this->addRoute("PUT", $url, $controller, $method, $permissions, $middleware);
    }
    public function delete(string $url, string $controller, string $method, $permissions = array(), $middleware = array())
    {
        $this->addRoute("DELETE", $url, $controller, $method, $permissions, $middleware);
    }
}
 |