3 * URL parser and mapper
9 * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com>
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
16 * * Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * * Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * * The names of the authors may not be used to endorse or promote products
22 * derived from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
25 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
26 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
32 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 * @package Net_URL_Mapper
38 * @author Bertrand Mansion <golgote@mamasam.com>
39 * @license http://opensource.org/licenses/bsd-license.php New BSD License
40 * @version CVS: $Id: Mapper.php,v 1.1 2007/03/28 10:23:04 mansion Exp $
41 * @link http://pear.php.net/package/Net_URL_Mapper
44 require_once 'Net/URL/Mapper/Path.php';
45 require_once 'Net/URL/Mapper/Exception.php';
48 * URL parser and mapper class
50 * This class takes an URL and a configuration and returns formatted data
51 * about the request according to a configuration parameter
54 * @package Net_URL_Mapper
55 * @author Bertrand Mansion <golgote@mamasam.com>
56 * @version Release: @package_version@
61 * Array of Net_URL_Mapper instances
64 private static $instances = array();
67 * Mapped paths collection
70 protected $paths = array();
73 * Prefix used for url mapping
76 protected $prefix = '';
79 * Optional scriptname if mod_rewrite is not available
82 protected $scriptname = '';
88 protected $id = '__default__';
92 * Constructor is private, you should use getInstance() instead.
94 private function __construct() { }
97 * Returns a singleton object corresponding to the requested instance id
98 * @param string Requested instance name
99 * @return Object Net_URL_Mapper Singleton
101 public static function getInstance($id = '__default__')
103 if (!isset(self::$instances[$id])) {
104 $m = new Net_URL_Mapper();
106 self::$instances[$id] = $m;
108 return self::$instances[$id];
112 * Returns the instance id
113 * @return string Mapper instance id
115 public function getId()
121 * Parses a path and creates a connection
122 * @param string The path to connect
123 * @param array Default values for path parts
124 * @param array Regular expressions for path parts
125 * @return object Net_URL_Mapper_Path
127 public function connect($path, $defaults = array(), $rules = array())
129 $pathObj = new Net_URL_Mapper_Path($path, $defaults, $rules);
130 $this->addPath($pathObj);
135 * Set the url prefix if needed
137 * Example: using the prefix to differenciate mapper instances
139 * $fr = Net_URL_Mapper::getInstance('fr');
140 * $fr->setPrefix('/fr');
141 * $en = Net_URL_Mapper::getInstance('en');
142 * $en->setPrefix('/en');
145 * @param string URL prefix
147 public function setPrefix($prefix)
149 $this->prefix = '/'.trim($prefix, '/');
153 * Set the scriptname if mod_rewrite not available
155 * Example: will match and generate url like
156 * - index.php/view/product/1
158 * $m = Net_URL_Mapper::getInstance();
159 * $m->setScriptname('index.php');
161 * @param string URL prefix
163 public function setScriptname($scriptname)
165 $this->scriptname = $scriptname;
169 * Will attempt to match an url with a defined path
171 * If an url corresponds to a path, the resulting values are returned
172 * in an array. If none is found, null is returned. In case an url is
173 * matched but its content doesn't validate the path rules, an exception is
177 * @return array|null array if match found, null otherwise
178 * @throws Net_URL_Mapper_InvalidException
180 public function match($url)
182 $nurl = '/'.trim($url, '/');
184 // Remove scriptname if needed
186 if (!empty($this->scriptname) &&
187 strpos($nurl, $this->scriptname) === 0) {
188 $nurl = substr($nurl, strlen($this->scriptname));
196 if (!empty($this->prefix)) {
197 if (strpos($nurl, $this->prefix) !== 0) {
200 $nurl = substr($nurl, strlen($this->prefix));
206 // Remove query string
208 if (($pos = strpos($nurl, '?')) !== false) {
209 $nurl = substr($nurl, 0, $pos);
215 // Make a list of paths that conform to route format
217 foreach ($this->paths as $path) {
218 $regex = $path->getFormat();
219 if (preg_match($regex, $nurl)) {
224 // Make sure one of the paths found is valid
226 foreach ($paths as $path) {
227 $regex = $path->getRule();
228 if (preg_match($regex, $nurl, $matches)) {
229 $values = $path->getDefaults();
230 array_shift($matches);
232 foreach ($matches as $k => $v) {
234 if (!is_int($k) && $v !== '') {
242 // A path conforms but does not validate
244 if (is_null($values) && !empty($paths)) {
245 $e = new Net_URL_Mapper_InvalidException('A path was found but is invalid.');
246 $e->setPath($paths[0]);
255 * Generate an url based on given parameters
257 * Will attempt to find a path definition that matches the given parameters and
258 * will generate an url based on this path.
260 * @param array Values to be used for the url generation
261 * @param array Key/value pairs for query string if needed
262 * @param string Anchor (fragment) if needed
263 * @return string|false String if a rule was found, false otherwise
265 public function generate($values = array(), $qstring = array(), $anchor = '')
267 // Use root path if any
269 if (empty($values) && isset($this->paths['/'])) {
270 return $this->scriptname.$this->prefix.$this->paths['/']->generate($values, $qstring, $anchor);
273 foreach ($this->paths as $path) {
275 foreach ($values as $k => $v) {
276 if ($path->hasKey($k, $v)) {
281 if (count($set) == count($values) &&
282 count($set) <= $path->getMaxKeys()) {
284 $req = $path->getRequired();
285 if (count(array_intersect(array_keys($set), $req)) != count($req)) {
288 $gen = $path->generate($set, $qstring, $anchor);
289 return $this->scriptname.$this->prefix.$gen;
296 * Returns defined paths
297 * @return array Array of paths
299 public function getPaths()
306 * This is probably only useful for testing
308 public function reset()
310 $this->paths = array();
315 * Add a new path to the mapper
316 * @param object Net_URL_Mapper_Path object
318 public function addPath(Net_URL_Mapper_Path $path)
320 $this->paths[$path->getPath()] = $path;