OSDN Git Service

e9bd478cd0567384b3e542882a49b1123f6d4aa6
[php-libraries/Router.git] / src / 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 Fig\Http\Message\RequestMethodInterface;
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      * RouteCollection that holds all Route objects
30      *
31      * @var RouteCollection
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             $name = $route->getName();
56             if (null !== $name) {
57                 $this->namedRoutes[$name] = $route;
58             }
59         }
60     }
61
62     /**
63      * Set the base _url - gets prepended to all route _url's.
64      * @param $basePath
65      */
66     public function setBasePath($basePath)
67     {
68         $this->basePath = rtrim($basePath, '/');
69     }
70
71     /**
72      * Matches the current request against mapped routes
73      */
74     public function matchCurrentRequest()
75     {
76         $requestMethod = (
77             isset($_POST['_method'])
78             && ($_method = strtoupper($_POST['_method']))
79             && in_array($_method, array(RequestMethodInterface::METHOD_PUT, RequestMethodInterface::METHOD_DELETE), true)
80         ) ? $_method : $_SERVER['REQUEST_METHOD'];
81
82         $requestUrl = $_SERVER['REQUEST_URI'];
83
84         // strip GET variables from URL
85         if (($pos = strpos($requestUrl, '?')) !== false) {
86             $requestUrl = substr($requestUrl, 0, $pos);
87         }
88
89         return $this->match($requestUrl, $requestMethod);
90     }
91
92     /**
93      * Match given request _url and request method and see if a route has been defined for it
94      * If so, return route's target
95      * If called multiple times
96      *
97      * @param string $requestUrl
98      * @param string $requestMethod
99      *
100      * @return bool|Route
101      */
102     public function match($requestUrl, $requestMethod = RequestMethodInterface::METHOD_GET)
103     {
104         $currentDir = dirname($_SERVER['SCRIPT_NAME']);
105
106         foreach ($this->routes->all() as $routes) {
107             // compare server request method with route's allowed http methods
108             if (! in_array($requestMethod, (array)$routes->getMethods(), true)) {
109                 continue;
110             }
111
112             if ('/' !== $currentDir) {
113                 $requestUrl = str_replace($currentDir, '', $requestUrl);
114             }
115
116             $route = rtrim($routes->getRegex(), '/');
117             $pattern = '@^' . preg_quote($this->basePath) . $route . '/?$@i';
118             if (!preg_match($pattern, $requestUrl, $matches)) {
119                 continue;
120             }
121
122             $params = array();
123
124             if (preg_match_all('/:([\w-%]+)/', $routes->getUrl(), $argument_keys)) {
125                 // grab array with matches
126                 $argument_keys = $argument_keys[1];
127
128                 // check arguments number
129
130                 if(count($argument_keys) !== (count($matches) -1)) {
131                     continue;
132                 }
133
134                 // loop trough parameter names, store matching value in $params array
135                 foreach ($argument_keys as $key => $name) {
136                     if (isset($matches[$key+1])) {
137                         $params[$name] = $matches[$key+1];
138                     }
139                 }
140             }
141
142             $routes->setParameters($params);
143             $routes->dispatch();
144
145             return $routes;
146         }
147
148         return false;
149     }
150
151     /**
152      * Reverse route a named route
153      *
154      * @param $routeName
155      * @param array $params Optional array of parameters to use in URL
156      *
157      * @throws Exception
158      *
159      * @return string The url to the route
160      */
161     public function generate($routeName, array $params = array())
162     {
163         // Check if route exists
164         if (!isset($this->namedRoutes[$routeName])) {
165             throw new Exception("No route with the name $routeName has been found.");
166         }
167
168         /** @var \PHPRouter\Route $route */
169         $route = $this->namedRoutes[$routeName];
170         $url = $route->getUrl();
171
172         // replace route url with given parameters
173         if ($params && preg_match_all('/:(\w+)/', $url, $param_keys)) {
174             // grab array with matches
175             $param_keys = $param_keys[1];
176
177             // loop trough parameter names, store matching value in $params array
178             foreach ($param_keys as $key) {
179                 if (isset($params[$key])) {
180                     $url = preg_replace('/:'.preg_quote($key,'/').'/', $params[$key], $url, 1);
181                 }
182             }
183         }
184
185         return $url;
186     }
187
188     /**
189      * Create routes by array, and return a Router object
190      *
191      * @param array $config provide by Config::loadFromFile()
192      * @return Router
193      */
194     public static function parseConfig(array $config)
195     {
196         $collection = new RouteCollection();
197         foreach ($config['routes'] as $name => $route) {
198             $collection->attachRoute(new Route($route[0], array(
199                 '_controller' => str_replace('.', '::', $route[1]),
200                 'methods' => $route[2],
201                 'name' => $name
202             )));
203         }
204
205         $router = new Router($collection);
206         if (isset($config['base_path'])) {
207             $router->setBasePath($config['base_path']);
208         }
209
210         return $router;
211     }
212 }