OSDN Git Service

Merge pull request #63 from endelwar/fix-named-routes
[php-libraries/Router.git] / src / PHPRouter / Router.php
1 <?php
2 /**
3  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
4  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
5  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
6  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
7  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
8  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
9  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
10  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
11  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
12  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
13  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
14  *
15  * This software consists of voluntary contributions made by many individuals
16  * and is licensed under the MIT license.
17  */
18 namespace PHPRouter;
19
20 use Exception;
21 use PHPRouter\RouteCollection;
22
23 /**
24  * Routing class to match request URL's against given routes and map them to a controller action.
25  */
26 class Router
27 {
28     /**
29      * Array that holds all Route objects
30      *
31      * @var array
32      */
33     private $routes = array();
34
35     /**
36      * Array to store named routes in, used for reverse routing.
37      * @var array
38      */
39     private $namedRoutes = array();
40
41     /**
42      * The base REQUEST_URI. Gets prepended to all route url's.
43      * @var string
44      */
45     private $basePath = '';
46
47     /**
48      * @param RouteCollection $collection
49      */
50     public function __construct(RouteCollection $collection)
51     {
52         $this->routes = $collection;
53
54         foreach ($this->routes->all() as $route) {
55             if (!is_null($route->getName())) {
56                 $this->namedRoutes[$route->getName()] = $route;
57             }
58         }
59     }
60
61     /**
62      * Set the base _url - gets prepended to all route _url's.
63      * @param $basePath
64      */
65     public function setBasePath($basePath)
66     {
67         $this->basePath = rtrim($basePath, '/');
68     }
69
70     /**
71      * Matches the current request against mapped routes
72      */
73     public function matchCurrentRequest()
74     {
75         $requestMethod = (
76             isset($_POST['_method'])
77             && ($_method = strtoupper($_POST['_method']))
78             && in_array($_method, array('PUT', 'DELETE'))
79         ) ? $_method : $_SERVER['REQUEST_METHOD'];
80
81         $requestUrl = $_SERVER['REQUEST_URI'];
82
83         // strip GET variables from URL
84         if (($pos = strpos($requestUrl, '?')) !== false) {
85             $requestUrl = substr($requestUrl, 0, $pos);
86         }
87
88         return $this->match($requestUrl, $requestMethod);
89     }
90
91     /**
92      * Match given request _url and request method and see if a route has been defined for it
93      * If so, return route's target
94      * If called multiple times
95      *
96      * @param string $requestUrl
97      * @param string $requestMethod
98      *
99      * @return bool|Route
100      */
101     public function match($requestUrl, $requestMethod = 'GET')
102     {
103         foreach ($this->routes->all() as $routes) {
104             // compare server request method with route's allowed http methods
105             if (!in_array($requestMethod, (array)$routes->getMethods())) {
106                 continue;
107             }
108
109             $currentDir = dirname($_SERVER['SCRIPT_NAME']);
110             if ($currentDir != '/') {
111                 $requestUrl = str_replace($currentDir, '', $requestUrl);
112             }
113
114             // check if request _url matches route regex. if not, return false.
115             if (!preg_match("@^" . $this->basePath . $routes->getRegex() . "*$@i", $requestUrl, $matches)) {
116                 continue;
117             }
118
119             $params = array();
120
121             if (preg_match_all("/:([\w-%]+)/", $routes->getUrl(), $argument_keys)) {
122                 // grab array with matches
123                 $argument_keys = $argument_keys[1];
124
125                 // loop trough parameter names, store matching value in $params array
126                 foreach ($argument_keys as $key => $name) {
127                     if (isset($matches[$key + 1])) {
128                         $params[$name] = $matches[$key + 1];
129                     }
130                 }
131
132             }
133
134             $routes->setParameters($params);
135             $routes->dispatch();
136
137             return $routes;
138         }
139
140         return false;
141     }
142
143     /**
144      * Reverse route a named route
145      *
146      * @param $routeName
147      * @param array $params Optional array of parameters to use in URL
148      *
149      * @throws Exception
150      *
151      * @return string The url to the route
152      */
153     public function generate($routeName, array $params = array())
154     {
155         // Check if route exists
156         if (!isset($this->namedRoutes[$routeName])) {
157             throw new Exception("No route with the name $routeName has been found.");
158         }
159
160         /** @var \PHPRouter\Route $route */
161         $route = $this->namedRoutes[$routeName];
162         $url = $route->getUrl();
163
164         // replace route url with given parameters
165         if ($params && preg_match_all("/:(\w+)/", $url, $param_keys)) {
166             // grab array with matches
167             $param_keys = $param_keys[1];
168
169             // loop trough parameter names, store matching value in $params array
170             foreach ($param_keys as $key) {
171                 if (isset($params[$key])) {
172                     $url = preg_replace("/:(\w+)/", $params[$key], $url, 1);
173                 }
174             }
175         }
176
177         return $url;
178     }
179
180     /**
181      * Create routes by array, and return a Router object
182      *
183      * @param array $config provide by Config::loadFromFile()
184      * @return Router
185      */
186     public static function parseConfig(array $config)
187     {
188         $collection = new RouteCollection();
189         foreach ($config['routes'] as $name => $route) {
190             $collection->attachRoute(new Route($route[0], array(
191                 '_controller' => str_replace('.', '::', $route[1]),
192                 'methods' => $route[2],
193                 'name' => $name
194             )));
195         }
196
197         $router = new Router($collection);
198         if (isset($config['base_path'])) {
199             $router->setBasePath($config['base_path']);
200         }
201
202         return $router;
203     }
204 }