Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
88.89% covered (warning)
88.89%
8 / 9
CRAP
93.94% covered (success)
93.94%
62 / 66
Router
0.00% covered (danger)
0.00%
0 / 1
88.89% covered (warning)
88.89%
8 / 9
35.27
93.94% covered (success)
93.94%
62 / 66
 __construct
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
8 / 8
 init
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
2 / 2
 serveUrl
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
11 / 11
 createController
100.00% covered (success)
100.00%
1 / 1
4
100.00% covered (success)
100.00%
10 / 10
 routeMatch
100.00% covered (success)
100.00%
1 / 1
10
100.00% covered (success)
100.00%
17 / 17
 prepareUrl
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
5 / 5
 doRouteAdd
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 routeAdd
0.00% covered (danger)
0.00%
0 / 1
6
0.00% covered (danger)
0.00%
0 / 4
 __get
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
7 / 7
<?php
namespace Tools;
/**
 * Will manager new connections to the server
 * and try to match the requests and the controllers
**/
class Router
{
    /**
     * @var string $rootPath
     * Contains the application's root path (ex: http://myshop.com/) with trailing slash
     * Can be accessed read-only via $instance->rootPath
    **/
    private $rootPath;
    /**
     * @var string $rootPath
     * Contains the application's root path (ex: /srv/http/myshop/) with trailing slash
     * Can be accessed read-only via $instance->rootUrl
    **/
    private $rootUrl;
    /**
     * @var string $requestUrl
     * Contains request
    **/
    private $requestUrl;
    /**
     * @var array ( array ( uri => controller ) ) $routes
     **/
    private $routes;
    /**
     * @var \Tools\Context $context
     * /core/tools/Context.php
     * Contains website's informations
    **/
    private $context;
    /**
     * @var array RouteParams
     * Contains route parameter
     * ex: /product/:id will contains {
     *    0 => product
     *    1 => [ID]
     *    ':id' => [ID]
    **/
    private $routeParams;
    /**
     * @var string $modulePath
     * Contains the module directory
    **/
    /**
     * @var string $moduleUrl
     * Contains the module Uri
    **/
    /**
     * @var string $themePath
     * Contains the theme directory
    **/
    /**
     * @var string $themeUrl
     * Contains the theme Uri
    **/
    /**
     * Create the router, initialize url and path
    **/
    public function __construct($server, $context)
    {
        $pos = strrpos($server["SCRIPT_NAME"], "/");
        $relativePath = (($pos === FALSE) ? "" : substr($server["SCRIPT_NAME"], 0, $pos));
        $this->rootPath = $server["DOCUMENT_ROOT"] . $relativePath . "/";
        $this->rootUrl = $server["REQUEST_SCHEME"] . "://" . $server["HTTP_HOST"] . $relativePath ."/";
        $this->requestUrl = substr($server["REQUEST_URI"], count($this->rootUrl) -1);
        $this->context = $context;
        $this->routes = array();
    }
    /**
     * Called after database initialization
     * Check the site url and redirect user if the HOST does not match
     * If the site url is not defined in database, do not redirect
    **/
    public function init($server)
    {
        $siteUrl = \Entity\Config::getConfig(null, "siteUrl");
        // @codeCoverageIgnoreStart
        // This code is tested under another process
        if ($siteUrl != $server["HTTP_HOST"] && $siteUrl !== null)
        {
            header("location: http://{$siteUrl}{$server['REQUEST_URI']}");
            die;
        }
        // @codeCoverageIgnoreEnd
    }
    /**
     * @return \Controller\AController controller
     * /core/controller/AController.php
     * Match request to a controller
     * return FALSE on failure (eg. 404)
    **/
    public function serveUrl()
    {
        //TODO trigger hook GET, POST
        $this->prepareUrl();
        $requestParams = explode("/", $this->requestUrl);
        foreach ($this->routes as $i)
        {
            $routeParams = explode("/", $i[0]);
            $p = $this->routeMatch($requestParams, $routeParams);
            if ($p === false)
                continue;
            $controller = $this->createController($i[1], $p);
            if ($controller)
                return $controller;
        }
        return false;
    }
    /**
     * Create and return Controller for the given route
     * @param $className
     * @param array $routeParameters
     * @return \Tools\AController on succes, false otherwise
    **/
    private function createController($className, $params)
    {
        if (!class_exists($className))
            return false;
        $this->routeParams = $params;
        $result = null;
        try
        {
            $result = new $className($this->context, $params);
            if (!($result instanceof \Tools\AController))
                return false;
        }
        catch (\Exception\Error404 $e)
        {
            return false;
        }
        return $result;
    }
    /**
     * Check if the request match route
     * @param array $request User request
     * @param array $route Route to check
     * @return array on success, false on failure
    **/
    private function routeMatch($request, $route)
    {
        $i = count($request);
        $params = array();
        if ($i != count($route))
            return false;
        while ($i)
        {
            $i--;
            if ($route[$i] == '' && $request[$i] == '')
                continue;
            if ($route[$i] == '' || $request[$i] == '')
                return false;
            if ($route[$i][0] != ':' && ($route[$i] != $request[$i]))
                return false;
            if ($route[$i][0] == ':')
                $params[$route[$i]] = $request[$i];
            $params[$i -1] = $request[$i];
        }
        return array_reverse($params);
    }
    /**
     * Append local routes to router
     * Will load CMS pages, categories page, products page, cart pages, etc.
    **/
    private function prepareUrl()
    {
        $fetcher = new \Entity\Cms();
        $pages = $fetcher->selects(null, array("order"));
        foreach ($pages as $i)
            $this->doRouteAdd($i->shurl, $i->controller);
    }
    /**
     * Add a route to the internal route list
     * Internal procedure
    **/
    private function doRouteAdd($route, $controller)
    {
        $this->routes[] = array($route, $controller);
    }
    /**
     * @param string $route Uri to match the controller
     * Uri can be formatted as '/:param/static'.
     * expl. '/product/:id/'
     * @param string $controller Controller class name.
     * new $controller() MUST return a \Tool\AController instance
     *
     * Add a route and a Controller to the list
     * Can only be called from `routerSetup' hook
    **/
    public function routeAdd($route, $controller)
    {
        if (!$this->context->hookManager->isInHook("routerSetup"))
            throw new \Exception("You can only add routes from `routerSetup' hook");
        $this->doRouteAdd($route, $controller);
    }
    /**
     * Getter
    **/
    public function __get($key)
    {
        switch ($key)
        {
        case "rootPath": return $this->rootPath; break;
        case "rootUrl": return $this->rootUrl; break;
        case "modulesPath": return $this->rootPath."content/modules/"; break;
        case "modulesUrl": return $this->rootUrl."content/modules/"; break;
        case "themesPath": return $this->rootPath."content/theme/"; break;
        case "themesUrl": return $this->rootUrl."content/theme/"; break;
        }
        throw new \Exception("Cannot access attribute {$key}");
    }
}