1: <?php
2:
3: /**
4: * Pry Framework
5: *
6: * LICENSE
7: *
8: * This source file is subject to the new BSD license that is bundled
9: * with this package in the file LICENSE.txt.
10: *
11: * @version $Revision: 276 $
12: */
13:
14: /**
15: * Router permettant la mise en place du pattern MVC
16: * Gère les routes classiques ainsi que les règles de routages
17: * <code>
18: * $router = Controller_Router::getInstance();
19: * $router->setPath(ROOT_PATH.'includes/controllers/'); // Chemin vers les controlleurs
20: * $router->addRule('test/regles/:id/hello',array('controller'=>'index','action'=>'withRule'));
21: * </code>
22: * Nécessite une règle de routage du type RewriteRule ^(.*)$ index.php?url=$1 [QSA,L] dans le serveur web
23: *
24: * @category Pry
25: * @package Controller
26: * @version 1.3.1
27: * @author Olivier ROGER <oroger.fr>
28: *
29: */
30: class Controller_Router
31: {
32:
33: /**
34: * Instance du router
35: * @static
36: * @var Controller_Router
37: */
38: static private $instance;
39:
40: /**
41: * Controller à utiliser. Par defaut index
42: * @var string
43: */
44: private $controller;
45:
46: /**
47: * Action du controller. Par défaut index
48: * @var string
49: */
50: private $action;
51:
52: /**
53: * Tableau des paramètres
54: * @var array
55: */
56: private $params;
57:
58: /**
59: * Liste des règles de routage
60: * @var array
61: */
62: private $rules;
63:
64: /**
65: * Chemin vers le dossier contenant les controllers
66: * @var string
67: */
68: private $path;
69:
70: /**
71: * Fichier à inclure
72: * @var string
73: */
74: private $file;
75:
76: /**
77: * Controller par defaut (index)
78: * @var string
79: */
80: private $defaultController;
81:
82: /**
83: * Action par defaut (index)
84: * @var string
85: */
86: private $defaultAction;
87:
88: /**
89: * Controller à appelé en cas d'erreur. Par defaut error
90: * @var string
91: */
92: private $errorController;
93:
94: /**
95: * Action à appelé en cas d'erreur. par defaut index
96: * @var string
97: */
98: private $errorAction;
99:
100: /**
101: * Le router gère t'il les url du type site.com/fr/controller/action
102: * @var boolean
103: */
104: private $isMultiLangue = false;
105:
106: /**
107: * Code langue choisie
108: * @var string
109: */
110: private $codeLangue = '';
111:
112: /**
113: * Liste des traduction d'url
114: * @var array
115: */
116: private $tradController;
117:
118: /**
119: * Singleton de la classe
120: * @return Controller_Router
121: */
122: public static function getInstance()
123: {
124: if (!isset(self::$instance))
125: self::$instance = new Controller_Router();
126: return self::$instance;
127: }
128:
129: /**
130: * Charge le controller demandé.
131: * Prend en compte les règles de routages si nécessaire
132: */
133: public function load()
134: {
135: $url = (!empty($_GET['url'])) ? $_GET['url'] : '';
136: $tabUrl = explode('/',$url);
137: $isCustom = false;
138:
139: //Suppression des éventuelles partie vide de l'url
140: $this->clear_empty_value($tabUrl);
141:
142: if (!empty($this->rules))
143: {
144: foreach ($this->rules as $key => $data) {
145: $params = $this->matchRules($key, $tabUrl);
146: if ($params)
147: {
148: $this->controller = $data['controller'];
149: $this->action = $data['action'];
150: $this->params = $params;
151: $isCustom = true;
152: break;
153: }
154: }
155: }
156:
157: if (!$isCustom)
158: $this->getRoute($tabUrl);
159:
160: $this->controller = (!empty($this->controller)) ? $this->controller : $this->defaultController;
161: $this->action = (!empty($this->action)) ? $this->action : $this->defaultAction;
162: $ctrlPath = str_replace('_', '/', $this->controller); // Gestion des sous dossiers dans les controllers
163: $this->file = realpath($this->path) . DIRECTORY_SEPARATOR . $ctrlPath . '.php';
164:
165: //is_file bien plus rapide que file_exists
166: if (!is_file($this->file))
167: {
168: header("Status: 404 Not Found");
169: $this->controller = $this->errorController;
170: $this->action = $this->errorAction;
171: $this->file = $this->path . $this->controller . '.php';
172: }
173:
174: //Inclusion du controller
175: include $this->file;
176:
177: $class = $this->controller . 'Controller';
178: if(!empty($this->codeLangue))
179: $controller = new $class($this->getParameters(),$this->codeLangue);
180: else
181: $controller = new $class($this->getParameters());
182:
183: if (!is_callable(array($controller, $this->action)))
184: $action = $this->defaultAction;
185: else
186: $action = $this->action;
187:
188: $controller->$action();
189: }
190:
191: /**
192: * Ajoute une règle de routage.
193: *
194: * @param string $rule Règles de routage : /bla/:param1/blabla/:param2/blabla
195: * @param array $target Cible de la règle : array('controller'=>'index','action'=>'test')
196: */
197: public function addRule($rule, $target)
198: {
199: if ($rule[0] != '/')
200: $rule = '/' . $rule; //Ajout du slashe de début si absent
201:
202: $this->rules[$rule] = $target;
203: }
204:
205: /**
206: * Vérifie si l'url correspond à une règle de routage
207: * @link http://blog.sosedoff.com/2009/07/04/simpe-php-url-routing-controller/
208: * @param string $rule
209: * @param array $dataItems
210: * @return boolean|array
211: */
212: public function matchRules($rule, $dataItems)
213: {
214: $ruleItems = explode('/', $rule);
215: $this->clear_empty_value($ruleItems);
216:
217: if (count($ruleItems) == count($dataItems))
218: {
219: $result = array();
220: foreach ($ruleItems as $rKey => $rValue) {
221: if ($rValue[0] == ':')
222: {
223: $rValue = substr($rValue, 1); //Supprime les : de la clé
224: $result[$rValue] = $dataItems[$rKey];
225: }
226: else
227: {
228: if ($rValue != $dataItems[$rKey])
229: return false;
230: }
231: }
232: if (!empty($result))
233: return $result;
234:
235: unset($result);
236: }
237: return false;
238: }
239:
240: /**
241: * Retourne l'action
242: * @return string
243: */
244: public function getAction()
245: {
246: return $this->action;
247: }
248:
249: /**
250: * Retourne le controller
251: * @return string
252: */
253: public function getController()
254: {
255: return $this->controller;
256: }
257:
258: /**
259: * Défini une route simple
260: * @param array $url
261: */
262: private function getRoute($url)
263: {
264: $items = $url;
265:
266: if (!empty($items))
267: {
268: if($this->isMultiLangue)
269: $this->codeLangue = array_shift ($items);
270:
271: $this->controller = array_shift($items);
272: $this->action = array_shift($items);
273: $size = count($items);
274: if($size >= 2)
275: for($i=0; $i< $size; $i += 2) {
276: $key = (isset($items[$i])) ? $items[$i] : $i;
277: $value = (isset($items[$i+1])) ? $items[$i+1] : null;
278: $this->params[$key] = $value;
279: }
280: else
281: $this->params = $items;
282:
283: //Permet d'avoir des URL multilangue
284: if(!empty($this->tradController))
285: {
286: if(isset($this->tradController[$this->codeLangue][$this->controller]['controllerName']))
287: {
288: $controller = $this->tradController[$this->codeLangue][$this->controller]['controllerName'];
289: if(!empty($controller))
290: $this->controller = $controller;
291: }
292:
293: if(isset($this->tradController[$this->codeLangue][$this->controller]['actionsNames'][$this->action]))
294: {
295: $action = $this->tradController[$this->codeLangue][$this->controller]['actionsNames'][$this->action];
296: if(!empty($action))
297: $this->action = $action;
298: }
299: }
300: }
301: }
302:
303: /**
304: * Défini le chemin des controllers
305: * @param string $path
306: */
307: public function setPath($path)
308: {
309: if (is_dir($path) === false)
310: {
311: throw new InvalidArgumentException('Controller invalide : ' . $path);
312: }
313:
314: $this->path = $path;
315: }
316:
317: /**
318: * Défini le router comme pouvant gérer ou non le multinlangue
319: * @param boolean $is
320: */
321: public function setMultiLangue($is)
322: {
323: $this->isMultiLangue = $is;
324: }
325:
326: /**
327: * Défini un tableau permettant d'avoir des URL multi langue.
328: * Format du tableau :
329: *
330: * @param array $trad format :
331: * $urlTraduction = array(
332: 'fr'=>array(
333: 'accueil'=>array(
334: 'controllerName' => 'index',
335: 'actionsNames' => array(
336: 'presentation' => 'index',
337: 'liste' => 'list',
338: 'recherche' => 'search'
339: )
340: )
341: ),
342: 'en'=>array(...));
343: */
344: public function setControllerTraduction($trad)
345: {
346: $this->tradController = $trad;
347: }
348:
349: /**
350: * Défini le controller et l'action par défaut
351: * @param string $controller
352: * @param string $action
353: */
354: public function setDefaultControllerAction($controller, $action)
355: {
356: $this->defaultController = $controller;
357: $this->defaultAction = $action;
358: }
359:
360: /**
361: * Défini le controller et l'actionen cas d'erreur
362: * @param string $controler
363: * @param string $action
364: */
365: public function setErrorControllerAction($controller, $action)
366: {
367: $this->errorController = $controller;
368: $this->errorAction = $action;
369: }
370:
371: /**
372: * Renvoi les paramètres disponibles
373: * @return array
374: */
375: public function getParameters()
376: {
377: return $this->params;
378: }
379:
380: /**
381: * Constructeur
382: */
383: private function __construct()
384: {
385: $this->rules = array();
386: $this->defaultController = 'index';
387: $this->defaultAction = 'index';
388: $this->errorController = 'error';
389: $this->errorAction = 'index';
390: }
391:
392: /**
393: * Supprime d'un tableau tous les élements vide
394: * @param array $array
395: */
396: private function clear_empty_value(&$array)
397: {
398: foreach ($array as $key => $value) {
399: if (empty($value) && $value != 0)
400: unset($array[$key]);
401: }
402: $array = array_values($array); // Réorganise les clés
403: }
404: }
405: