├── .gitignore ├── .htaccess ├── src ├── Middlewares │ ├── MiddlewareInterface.php │ └── Middleware.php ├── Http │ ├── HttpDataTrait.php │ ├── Response.php │ └── Request.php ├── Container.php ├── Render.php ├── App.php ├── Route.php └── Router.php ├── CONTRIBUTING.md ├── composer.json ├── README.md ├── LICENSE.md └── git-update-fork /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /views 3 | index.php -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{SCRIPT_FILENAME} !-f 3 | RewriteCond %{SCRIPT_FILENAME} !-d 4 | RewriteRule ^(.*)$ index.php/$1 [QSA,L] -------------------------------------------------------------------------------- /src/Middlewares/MiddlewareInterface.php: -------------------------------------------------------------------------------- 1 | Todos os pull request devem aderir ao padrão PSR-2 de estilo
-------------------------------------------------------------------------------- /src/Http/HttpDataTrait.php: -------------------------------------------------------------------------------- 1 | data[$key])); 11 | } 12 | 13 | public function __set($key, $val) 14 | { 15 | $this->data[$key] = $val; 16 | } 17 | 18 | 19 | public function __get($key) 20 | { 21 | return $this->data[$key]; 22 | } 23 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lukasdev/drouter", 3 | "description": "Um sistema simplista de roteamento, com o intuito de ser utilizado em aplicacoes web pequenas e webservices REST", 4 | "keywords": ["router", "REST", "microframework", "api"], 5 | "homepage": "https://github.com/lukasdev/DRouter", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Lucas Silva", 10 | "email": "dev.lucassilva@gmail.com", 11 | "role": "Developer" 12 | } 13 | ], 14 | "support": { 15 | "email": "dev.lucassilva@gmail.com" 16 | }, 17 | "require": { 18 | "php": ">=7.0.0" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "DRouter\\": "src/" 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DRouter 2 | 3 | Um sistema simplista de roteamento, com o intuito de ser utilizado em aplicações 4 | web pequenas e webservices REST. 5 | 6 | ## Instalação 7 | via composer 8 | 9 | ``` bash 10 | $ composer require lukasdev/drouter 11 | ``` 12 | 13 | ## Utilização 14 | 15 |A pagina que você procura não está aqui, verifique a url!
128 | 129 | '; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Http/Request.php: -------------------------------------------------------------------------------- 1 | 8 | * @copyright 2016 Lucas Silva 9 | * @link http://www.downsmaster.com 10 | * @version 2.0.0 11 | * 12 | * MIT LICENSE 13 | */ 14 | namespace DRouter\Http; 15 | 16 | class Request 17 | { 18 | use HttpDataTrait; 19 | 20 | /** 21 | * Retorna o metodo atual ou GET por padrão 22 | * @return string 23 | */ 24 | public function getMethod() 25 | { 26 | return (isset($_SERVER['REQUEST_METHOD'])) ? $_SERVER['REQUEST_METHOD'] : 'GET'; 27 | } 28 | 29 | /** 30 | * Retorna o content-type do request atual 31 | * @return string 32 | */ 33 | public function getContentType() 34 | { 35 | return (isset($_SERVER['CONTENT_TYPE'])) ? $_SERVER['CONTENT_TYPE'] : null; 36 | } 37 | 38 | /** 39 | * Retorna os dados "crus" da requisição http para ser tratado 40 | * @return string 41 | */ 42 | public function getRawData() { 43 | return file_get_contents('php://input'); 44 | } 45 | 46 | /** 47 | * Retorna a coversão dos dados json do request em array 48 | * @return array 49 | */ 50 | public function getJsonRequest() { 51 | return json_decode($this->getRawData(), true); 52 | } 53 | 54 | /** 55 | * Retorna o conteudo do request parseado para GET, POST, PUT e DELETE 56 | * ou lança uma exceção caso o tipo de content-type seja inválido 57 | * @return array 58 | */ 59 | public function getParsedBody() 60 | { 61 | if (!in_array($this->getMethod(), ['GET', 'POST'])) { 62 | if ($this->getContentType() == 'application/x-www-form-urlencoded') { 63 | $input_contents = $this->getRawData(); 64 | 65 | if (function_exists('mb_parse_str')) { 66 | mb_parse_str($input_contents, $post_vars); 67 | } else { 68 | parse_str($input_contents, $post_vars); 69 | } 70 | if (count($_GET) > 0) { 71 | $post_vars = array_merge($post_vars, $_GET); 72 | } 73 | return $post_vars; 74 | } else { 75 | throw new \UnexpectedValueException('Content-type não aceito'); 76 | } 77 | } elseif ($this->getMethod() == 'POST') { 78 | if (count($_GET) > 0) { 79 | $_POST = array_merge($_POST, $_GET); 80 | } 81 | 82 | return $_POST; 83 | } elseif ($this->getMethod() == 'GET') { 84 | return $_GET; 85 | } 86 | } 87 | 88 | /** 89 | * Retorna o valor de um indice no corpo do request, caso exista! 90 | * @param $key string 91 | */ 92 | public function get($key) 93 | { 94 | $data = $this->getParsedBody(); 95 | if (isset($data[$key])) { 96 | return $data[$key]; 97 | } 98 | } 99 | 100 | /** 101 | * Retorna o RequestUri atual 102 | * @return string 103 | */ 104 | public function getRequestUri() 105 | { 106 | if (isset($_SERVER['ORIG_PATH_INFO'])) { 107 | $pathInfo = $_SERVER['ORIG_PATH_INFO']; 108 | } elseif (isset($_SERVER['PATH_INFO'])) { 109 | $pathInfo = $_SERVER['PATH_INFO']; 110 | } 111 | 112 | //correção para alguns hosts 113 | if (isset($pathInfo)) { 114 | $pathInfo = str_replace('/index.php', '', $pathInfo); 115 | } 116 | 117 | $rota = (!isset($pathInfo)) ? '/' : strip_tags(trim($pathInfo)); 118 | 119 | return $rota; 120 | } 121 | 122 | /** 123 | * Define a url default do site, para o caso de ignorar um prefixo inicial 124 | * exemplo: site.com/v1, o v1 pode ser ignorado ao ser passado como parametro 125 | * esta função nao aceita parametros dinamicos, ex: v1/:number 126 | */ 127 | public function setDefaultPath($pattern = null){ 128 | if ($pattern) { 129 | $pattern = rtrim($pattern, '/'); 130 | $pattern = ltrim($pattern, '/'); 131 | 132 | $uri = ''; 133 | if (isset($_SERVER['ORIG_PATH_INFO'])) { 134 | $uri = $_SERVER['ORIG_PATH_INFO']; 135 | $uriRel = &$_SERVER['ORIG_PATH_INFO']; 136 | } elseif (isset($_SERVER['PATH_INFO'])) { 137 | $uri = $_SERVER['PATH_INFO']; 138 | $uriRel = &$_SERVER['PATH_INFO']; 139 | } 140 | 141 | $exp = array_values(array_filter(explode('/', $uri))); 142 | $expPattern = array_values(array_filter(explode('/', $pattern))); 143 | 144 | foreach ($expPattern as $i => $key) { 145 | if ($exp[$i] != $key) { 146 | $redirect = $this->getRoot().'/'.$pattern; 147 | header('Location: '.$redirect); 148 | die; 149 | } 150 | 151 | unset($exp[$i]); 152 | } 153 | 154 | $newUri = '/'.implode('/', $exp); 155 | #echo $newUri; 156 | $uriRel = $newUri; 157 | } 158 | } 159 | 160 | /** 161 | * Retorna a base da aplicação, exemplo /projetos/aplicacao 162 | * caso a aplicaçao esteja em localhost/projetos/aplicacao 163 | */ 164 | public function getRoot() 165 | { 166 | $base = substr(explode('index.php', $_SERVER['SCRIPT_NAME'])[0], 0, -1); 167 | return $base; 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/App.php: -------------------------------------------------------------------------------- 1 | 7 | * @copyright 2016 Lucas Silva 8 | * @link http://www.downsmaster.com 9 | * @version 2.0.0 10 | * 11 | * MIT LICENSE 12 | */ 13 | namespace DRouter; 14 | 15 | use DRouter\Http\Request; 16 | use DRouter\Http\Response; 17 | 18 | class App 19 | { 20 | /** 21 | * Objeto \DRouter\Router 22 | * @var $router Router 23 | */ 24 | protected $router; 25 | 26 | /** 27 | * Objeto \DRouter\Request 28 | * @var $request Request 29 | */ 30 | protected $request; 31 | 32 | /** 33 | * Objeto \DRouter\Response 34 | * @var $request Response 35 | */ 36 | protected $response; 37 | 38 | /** 39 | * Objeto DRouter\Container 40 | * @var $container Container 41 | */ 42 | protected $container; 43 | 44 | /** 45 | * Objeto \DRouter\Render 46 | * @var $render Render 47 | */ 48 | public $render; 49 | 50 | /** 51 | * Pagina notFound modificada 52 | * @var $notFoundModified false|callable 53 | */ 54 | protected $notFoundModified = false; 55 | 56 | /** 57 | * Exceptions adicionais da App a serem lançadas! 58 | * @var $addedExceptions array 59 | */ 60 | protected $addedExceptions = array(); 61 | 62 | public function __construct($paramsContainer = array()) 63 | { 64 | $this->request = new Request(); 65 | $this->response = new Response(); 66 | $this->render = new Render(); 67 | $this->router = new Router($this->request); 68 | 69 | $content = [ 70 | 'request' => $this->request, 71 | 'response' => $this->response, 72 | 'render' => $this->render, 73 | 'router' => $this->router 74 | ]; 75 | $params = array_merge($paramsContainer, $content); 76 | $this->container = new Container($params); 77 | } 78 | 79 | /** 80 | * @return \DRouter\Http\Request 81 | */ 82 | public function getRequest() 83 | { 84 | return $this->request; 85 | } 86 | /** 87 | * @return \DRouter\Container 88 | */ 89 | public function getContainer() 90 | { 91 | return $this->container; 92 | } 93 | 94 | /** 95 | * Recebe um callable e retorna sua referencia de acordo 96 | * @return callable 97 | */ 98 | private function validCallable($callable) 99 | { 100 | if (is_callable($callable)) { 101 | if ($callable instanceof \Closure) { 102 | $callable = $callable->bindTo($this->container); 103 | } 104 | 105 | return $callable; 106 | } elseif (is_string($callable) && count(explode(':', $callable)) == 2) { 107 | return $callable; 108 | } 109 | 110 | $this->addedExceptions['\InvalidArgumentException'] = 'Callable inválido'; 111 | return false; 112 | } 113 | 114 | /** 115 | * Emula metodos get, post, put, delete e group do objeto Router 116 | */ 117 | public function __call($method, $args) 118 | { 119 | $methodUpper = strtoupper($method); 120 | $accepted = $this->router->getRequestAccepted(); 121 | 122 | if (in_array($methodUpper, $accepted)) { 123 | if (count($args) == 3) { 124 | $conditions = $args[2]; 125 | } elseif (count($args) == 2) { 126 | $conditions = array(); 127 | } 128 | if ($args[0] == '/[:options]') { 129 | $this->addedExceptions['\InvalidArgumentException'] = 'Route pattern /[:options] inválido'; 130 | } else { 131 | $callable = $this->validCallable($args[1]); 132 | 133 | return $this->router->route($methodUpper, $args[0], $callable, $conditions); 134 | } 135 | } elseif ($method == 'group' && count($args) == 2) { 136 | $callable = $this->validCallable($args[1]); 137 | return $this->router->group($args[0], $callable); 138 | } else { 139 | $this->addedExceptions['\Exception'] = 'O metodo '.$method.' não existe'; 140 | } 141 | } 142 | 143 | /** 144 | * Define uma pagina notfoud 145 | * @param callable $fnc 146 | */ 147 | public function notFound($fnc) 148 | { 149 | if (is_callable($fnc)) { 150 | if ($fnc instanceof \Closure) { 151 | $this->notFoundModified = $fnc; 152 | } else { 153 | $this->addedExceptions['\InvalidArgumentException'] = 'O callable do metodo notFound deve ser um closure!'; 154 | } 155 | } else { 156 | $this->addedExceptions['\InvalidArgumentException'] = 'App::notFound, callable invalido'; 157 | } 158 | } 159 | 160 | /** 161 | * Retorna o path root via request 162 | * @return string 163 | */ 164 | public function root() 165 | { 166 | return $this->request->getRoot(); 167 | } 168 | 169 | /** 170 | * Lança exceções adicionais do objeto App. 171 | */ 172 | private function runAddedExceptions() 173 | { 174 | if (!empty($this->addedExceptions)) { 175 | foreach ($this->addedExceptions as $exception => $message) { 176 | throw new $exception($message); 177 | break; 178 | } 179 | } 180 | } 181 | 182 | 183 | public function add(array $routeNames, array $middewares){ 184 | $this->router->add($routeNames, $middewares); 185 | } 186 | /** 187 | * Da inicio a App. Executando as rotas criadas, renderizando uma pagina 404 188 | * ou exibindo a mensagem de uma exceção que tenha sido lançada 189 | */ 190 | public function run() 191 | { 192 | /*echo '';
193 | print_r($this->router->getRoutes());
194 | die;*/
195 | try {
196 | $this->runAddedExceptions();
197 |
198 | if ($this->router->dispatch()) {
199 | $this->router->execute($this->container);
200 | } else {
201 | if ($this->notFoundModified) {
202 | $fnc = $this->notFoundModified->bindTo($this->container);
203 | $fnc();
204 | } else {
205 | $this->render->renderNotFoundPage();
206 | }
207 | }
208 | } catch (\Exception $e) {
209 | echo $e->getMessage();
210 | }
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/Route.php:
--------------------------------------------------------------------------------
1 |
8 | * @copyright 2016 Lucas Silva
9 | * @link http://www.downsmaster.com
10 | * @version 2.0.0
11 | *
12 | * MIT LICENSE
13 | */
14 | namespace DRouter;
15 |
16 | class Route
17 | {
18 | /**
19 | * String com o nome da rota em questão para uso futuro na aplicação
20 | * @var $name string
21 | */
22 | protected $name;
23 | /**
24 | * String com o padrão da rota, exemplo /user/:id
25 | * @var $pattern string
26 | */
27 | protected $pattern;
28 |
29 | /**
30 | * Callable a ser executado na rota. Pode ser [obj, method], 'fnc_name' ou obj
31 | * @var $callable callable|string|object
32 | */
33 | protected $callable;
34 |
35 | /**
36 | * Array com condições para parametros desta rota, exemplo
37 | * ['id' => '[\d]{1,8}'] para que o parametro :id seja um digito até 8
38 | * caracteres
39 | * @var $conditions array
40 | */
41 | protected $conditions = array();
42 |
43 | /**
44 | * Array associativo com os parametros desta rota, exemplo
45 | * ['id' => '5', 'tipo' => 8]
46 | * @var $params array
47 | */
48 | protected $params = array();
49 |
50 | /**
51 | * Array contendo todas as middleawres a serem executadas antes desta
52 | * rota
53 | * @var $middlewares array
54 | */
55 | private $middlewares = [];
56 |
57 | /**
58 | * Propriedade contendo caso exista, o prefixo de group dessa rota
59 | * @var null | string
60 | */
61 | private $groupPrefix = null;
62 |
63 | private $options = [];
64 |
65 | public function __construct($pattern, $callable, array $conditions)
66 | {
67 | $this->pattern = $pattern;
68 | $this->callable = $callable;
69 | $this->conditions = $conditions;
70 | }
71 |
72 | /**
73 | * Seta o prefixo de group da rota
74 | */
75 | public function setGroupPrefix($prefix)
76 | {
77 | $this->groupPrefix = $prefix;
78 | }
79 |
80 | /**
81 | * Retorna o prefixo de group da rota
82 | */
83 | public function getGroupPrefix()
84 | {
85 | return $this->groupPrefix;
86 | }
87 |
88 | /**
89 | * Adiciona um determinado middleware ao array de middlewares
90 | */
91 | public function addMiddleware($middleware)
92 | {
93 | $this->middlewares[] = $middleware;
94 | }
95 |
96 | /**
97 | * Adiciona um conjunto de middlewares ao array de middlewares
98 | */
99 | public function addMiddlewares(array $middlewares)
100 | {
101 | $this->middlewares = $middlewares;
102 | }
103 |
104 | /**
105 | * Retorna o array de middlewares
106 | */
107 | public function getMiddlewares()
108 | {
109 | return $this->middlewares;
110 | }
111 |
112 | /**
113 | * Configura o nome da rota em questão
114 | * @var $name string
115 | */
116 | public function setName($name){
117 | $this->name = $name;
118 | }
119 |
120 | /**
121 | * Retorna o nome da rota em questão
122 | */
123 | public function getName(){
124 | return $this->name;
125 | }
126 |
127 | /**
128 | * Retorna o callable da rota atual
129 | * @return callable
130 | */
131 | public function getCallable()
132 | {
133 | return $this->callable;
134 | }
135 |
136 | /**
137 | * Retorna os parametros encontrados desta rota
138 | * @return array
139 | */
140 | public function getParams()
141 | {
142 | return $this->params;
143 | }
144 |
145 | /**
146 | * Retorna os padrão desta rota
147 | * @return string
148 | */
149 | public function getPattern()
150 | {
151 | return $this->pattern;
152 | }
153 |
154 | public function getParamNames()
155 | {
156 | preg_match_all('@:([\w]+)@', $this->pattern, $paramNames, PREG_PATTERN_ORDER);
157 | return $paramNames[0];
158 | }
159 |
160 |
161 | public function getOptions() {
162 | return $this->options;
163 | }
164 |
165 | /**
166 | * Verifica se o padrão da rota coincide com o padrão escrito na
167 | * url da aplicação, e guarda os parametros encontrados sob suas
168 | * respectivas - caso existentes - restrições
169 | * @param $resourceUri
170 | * @return bolean
171 | */
172 | public function match($resourceUri)
173 | {
174 | $paramNames = $this->getParamNames();
175 |
176 | $pattern = $this->getPattern();
177 |
178 | if (preg_match('/\[:options\]/', $pattern)) {
179 | $exp = explode('[:options]', $pattern);
180 | $estatico = $exp[0];
181 | $estaticoDinamico = explode($estatico, $resourceUri);
182 | if (count($estaticoDinamico) == 2) {
183 | $estaticoUrl = explode($estaticoDinamico[1], $resourceUri);
184 | $estaticoUrl = $estaticoUrl[0];
185 |
186 | if ($estaticoUrl == $estatico){
187 | //estou em uma rota dinamica valida
188 | $dinamico = $estaticoDinamico[1];
189 | $this->options = explode('/', $dinamico);
190 | return true;
191 | }
192 | } else {
193 | return false;
194 | }
195 | } else {
196 | $patternAsRegex = preg_replace_callback('@:[\w]+@', [$this, 'convertToRegex'], $this->pattern);
197 | if (substr($this->pattern, -1) === '/') {
198 | $patternAsRegex = $patternAsRegex . '?';
199 | }
200 | $patternAsRegex = '@^' . $patternAsRegex . '$@';
201 |
202 | if (preg_match($patternAsRegex, $resourceUri, $paramValues)) {
203 | array_shift($paramValues);
204 |
205 | if (count($paramValues) > 0) {
206 | foreach ($paramNames as $index => $value) {
207 | $this->params[substr($value, 1)] = urldecode($paramValues[$index]);
208 | }
209 | }
210 | return true;
211 | } else {
212 | return false;
213 | }
214 | }
215 | }
216 |
217 | /**
218 | * Converte uma variavel em regex, exemplo :id pode virar
219 | * ([a-zA-Z0-9_\-\.]+) ou pode virar ([\d]{1,8}) caso seja
220 | * encontrada tal restrição na $this->conditions
221 | * @param $matches array
222 | */
223 | public function convertToRegex($matches)
224 | {
225 | $key = str_replace(':', '', $matches[0]);
226 | if (array_key_exists($key, $this->conditions)) {
227 | return '(' . $this->conditions[$key] . ')';
228 | } else {
229 | return '([a-zA-Z0-9_\-\.]+)';
230 | }
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/Router.php:
--------------------------------------------------------------------------------
1 |
8 | * @copyright 2016 Lucas Silva
9 | * @link http://www.downsmaster.com
10 | * @version 2.0.0
11 | *
12 | * MIT LICENSE
13 | */
14 | namespace DRouter;
15 |
16 | use DRouter\Http\Request;
17 | use DRouter\Route;
18 | use DRouter\Middlewares\Middleware;
19 |
20 | class Router
21 | {
22 | /**
23 | * Array que define os metodos aceitos e guarda suas respectivas rotas
24 | * @var $routes array
25 | */
26 | protected $routes = array(
27 | 'GET' => array(),
28 | 'POST' => array(),
29 | 'PUT' => array(),
30 | 'DELETE' => array()
31 | );
32 |
33 | /**
34 | * Objeto DRouter\Request
35 | * @var $request Request
36 | */
37 | protected $request;
38 |
39 | /**
40 | * Objeto DRouter\Route - Rota a ser despachada
41 | * @var $machedRoute Route
42 | */
43 | protected $matchedRoute;
44 |
45 | /**
46 | * Prefixo do grupo de rota
47 | * @var $routePrefix null|string
48 | */
49 | protected $routePrefix = null;
50 |
51 | /**
52 | * Array de nomenclatura de rotas no formato [METHOD:index] = name
53 | * @var $routeNames array
54 | */
55 | protected $routeNames = array();
56 |
57 | /**
58 | * Ultimo metodo utilizado em uma rota
59 | * @var $lastRouteMethod null|string
60 | */
61 | protected $lastRouteMethod = null;
62 |
63 | /**
64 | * Rotas candidatas a serem despachadas
65 | * @var $candidateRoutes array
66 | */
67 | protected $candidateRoutes = [];
68 |
69 | /**
70 | * Array associativo de rotas a middlewares
71 | * @var $middlewares array
72 | */
73 | protected $middlewares = [];
74 |
75 |
76 | /**
77 | * Array contendo group prefixes e seus respectivos middlewares
78 | */
79 | protected $groupMiddlewares = [];
80 |
81 | /**
82 | * Group atual em utilização na aplicação
83 | */
84 | protected $currentGroup = null;
85 |
86 | public function __construct(Request $request)
87 | {
88 | $this->request = $request;
89 | }
90 |
91 | /**
92 | * Valida os paths das rotas, retirando a ultima barra caso exista
93 | * para evitar conflitos no dispatch
94 | * @param $path string
95 | */
96 | private function validatePath($path)
97 | {
98 | $last = strlen($path)-1;
99 | if ($path[$last] == '/') {
100 | $path = substr($path, 0, -1);
101 | }
102 | return $path;
103 | }
104 |
105 | /**
106 | * Define uma rota sob um metodo criando sua representação no objeto Route
107 | * @param $method string
108 | * @param $pattern string
109 | * @param $callable callable
110 | * @param $conditions null|array
111 | */
112 | public function route($method, $pattern, $callable, $conditions)
113 | {
114 | //reseta currentGroups para rotas futuras não herdarem seus middlewares
115 | $this->currentGroup = null;
116 |
117 | $method = strtoupper($method);
118 | $pattern = $this->validatePath($pattern);
119 | if (!is_null($this->routePrefix)) {
120 | $pattern = $this->routePrefix.$pattern;
121 | }
122 |
123 | $objRoute = new Route($pattern, $callable, $conditions);
124 | if (!is_null($this->routePrefix)) {
125 | $objRoute->setGroupPrefix($this->routePrefix);
126 | }
127 |
128 | $this->routes[$method][] = $objRoute;
129 | $this->lastRouteMethod = $method;
130 | return $this;
131 | }
132 |
133 |
134 | public function getRoutes() {
135 | return $this->routes;
136 | }
137 |
138 | /**
139 | * Define o prefixo para agrupamento das rotas
140 | * @param $prefix string
141 | * @param $fnc callable Closure
142 | */
143 | public function group($prefix, $fnc)
144 | {
145 | $this->routePrefix = $prefix;
146 | if ($fnc instanceof \Closure) {
147 | $fnc();
148 | } else {
149 | throw new \InvalidArgumentException('Callable do metodo group DEVE ser um Closure');
150 | }
151 | $this->routePrefix = null;
152 | $this->groupMiddlewares[$prefix] = [];
153 | $this->currentGroup = $prefix;
154 |
155 | return $this;
156 | }
157 |
158 | /**
159 | * Define o nome de uma rota recem criada
160 | * @param $routeName string
161 | */
162 | public function setName($routeName)
163 | {
164 | $lastMethod = $this->lastRouteMethod;
165 | $lastIndex = count($this->routes[$lastMethod])-1;
166 | $indexName = $lastMethod.':'.$lastIndex;
167 | $this->routeNames[$indexName] = $routeName;
168 |
169 | $rota = $this->routes[$lastMethod][$lastIndex];
170 | $rota->setName($routeName);
171 | $this->routes[$lastMethod][$lastIndex] = $rota;
172 |
173 | return $this;
174 | }
175 |
176 | /**
177 | * Encontra um objeto de uma rota por seu nome
178 | */
179 | public function findRouteByName($routeName) {
180 | $routePath = array_search($routeName, $this->routeNames);
181 | list($method, $index) = explode(':', $routePath);
182 |
183 | return $this->routes[$method][$index];
184 | }
185 |
186 | /**
187 | * Encontra o ultimo objeto de rota declarada idependente de seu name
188 | */
189 | private function findLastRoute()
190 | {
191 | $method = $this->lastRouteMethod;
192 | $index = count($this->routes[$method])-1;
193 |
194 | return $this->routes[$method][$index];
195 | }
196 |
197 | /**
198 | * Adiciona middlewares sob varias circunstancias (rotas, globais, names e group)
199 | */
200 | public function add()
201 | {
202 | $args = func_get_args();
203 |
204 | if (count($args) == 1) {
205 | list($middleware) = $args;
206 | if (!is_null($this->currentGroup)) {
207 | //middlewares no group
208 | $this->groupMiddlewares[$this->currentGroup][] = $middleware;
209 | } else {
210 | //middlewares na rota
211 | $lastRoute = $this->findLastRoute();
212 | $lastRoute->addMiddleware($middleware);
213 | }
214 |
215 | } elseif(count($args) > 1) {
216 | list($routeNames, $middlewares) = $args;
217 | foreach ($routeNames as $routeName) {
218 | if (isset($this->middlewares[$routeName])) {
219 | $currentMiddlewares = $this->middlewares[$routeName];
220 | $this->middlewares[$routeName] = array_merge($middlewares, $currentMiddlewares);
221 | } else {
222 | $this->middlewares[$routeName] = $middlewares;
223 | }
224 | }
225 | }
226 | return $this;
227 | }
228 |
229 | /**
230 | * Retorna os middlewares globais para posterior chamada
231 | * @return array
232 | */
233 | public function getMiddlewares()
234 | {
235 | return $this->middlewares;
236 | }
237 | /**
238 | * Encontra uma rota pelo seu nome dentro do array de rotas
239 | * @param $routeName string
240 | * @return DRouter\Route
241 | */
242 | protected function getRoute($routeName)
243 | {
244 | $routeIndex = array_search($routeName, $this->routeNames);
245 | if ($routeIndex == false) {
246 | throw new \RuntimeException('Rota '.$routeName.' não encontada');
247 | } else {
248 | $split = explode(':', $routeIndex);
249 | $rota = $this->routes[$split[0]][$split[1]];
250 |
251 | return $rota;
252 | }
253 | }
254 |
255 | /**
256 | * Retorna o callable de uma rota pelo seu nome
257 | * @param $routeName
258 | * @return callable
259 | */
260 | public function getRouteCallable($routeName)
261 | {
262 | $route = $this->getRoute($routeName);
263 | return $route->getCallable();
264 | }
265 |
266 | /**
267 | * Retorna o path até uma rota nomeada, trocando seus parametros
268 | * caso necessário
269 | * @param $routeName string
270 | * @param $params array
271 | * @return string
272 | */
273 | public function pathFor($routeName, $params = array())
274 | {
275 | if ($rota = $this->getRoute($routeName)) {
276 | $pattern = $rota->getPattern();
277 | $qtdParams = count($rota->getParamNames());
278 |
279 | $withOptions = preg_match('/\[:options\]/', $pattern);
280 |
281 | if ($qtdParams > 0 && count($params) == 0) {
282 | throw new \RuntimeException('A rota '.$routeName.' requer '.$qtdParams.' parametro(s)!');
283 | }
284 |
285 | if ($withOptions && count($params) > 0) {
286 | $pattern = str_replace('[:options]', '', $pattern);
287 | $pattern .= implode('/', $params);
288 | } elseif (count($params) > 0) {
289 | foreach ($params as $key => $value) {
290 | $pattern = str_replace(':'.$key, $value, $pattern);
291 | }
292 | }
293 | return $this->request->getRoot().$pattern;
294 | }
295 | }
296 |
297 | /**
298 | * Efetua um redirecionamento para um path, passando gets opcionais
299 | * convertidos de array, como parametros
300 | * @param string $routeName
301 | * @param array $query
302 | * @param array $params
303 | */
304 | public function redirectTo($routeName, $query = array(), $params = array())
305 | {
306 | $path = $this->pathFor($routeName, $params);
307 |
308 | if (!is_array($query)) {
309 | throw new \UnexpectedValueException('Router::redirectTo A query deve ser um array!');
310 | }
311 |
312 | if (count($query) > 0) {
313 | $path = $path.'?'.http_build_query($query);
314 | }
315 | $path = ($path == '') ? '/' : $path;
316 | header("Location: ".$path);
317 | die;
318 | }
319 |
320 | /**
321 | * Retorna array com tipos de requests aceitos pelo roteamento
322 | * @return array
323 | */
324 | public function getRequestAccepted()
325 | {
326 | return array_keys($this->routes);
327 | }
328 |
329 | /**
330 | * Pelo request method atual, navega pelas rotas definidas
331 | * E encontra a rota que coincidir com o padrão do RequestUri atual
332 | * Guardando-a no array de rotas candidatas
333 | * @return bolean
334 | */
335 | public function dispatch()
336 | {
337 | $requestUri = $this->validatePath($this->request->getRequestUri());
338 |
339 | foreach ($this->routes[$this->request->getMethod()] as $rota) {
340 | if ($rota->match($requestUri)) {
341 | $this->candidateRoutes[] = $rota;
342 | }
343 | }
344 |
345 | if (count($this->candidateRoutes) > 0) {
346 | $this->dispatchCandidateRoutes($requestUri);
347 |
348 | return true;
349 | }
350 | return false;
351 | }
352 |
353 | /**
354 | * Retorno o que não for variavel de uma pattern, exemplo: /categoria/:slug
355 | * Nestecaso :slug é umavariavel, e eu retornarei "categoria"
356 | * @param $pattern string
357 | */
358 | public function getNonVariables($pattern) {
359 | $exp = explode('/',$pattern);
360 | $retorno = [];
361 | foreach($exp as $i => $v) {
362 | if(!preg_match('/^[\:]/i', $v)) {
363 | $retorno[$i] = $v;
364 | }
365 | }
366 |
367 | return $retorno;
368 | }
369 |
370 | /**
371 | * Determina qual rota deve ser despachada, com base em sua similaridade com
372 | * a request URI,para evitar conflitos entre rotas parecidas.
373 | * @param $requestUri string
374 | */
375 | public function dispatchCandidateRoutes($requestUri) {
376 | $expUri = explode('/',$requestUri);
377 | $similaridades = [];
378 |
379 | if (count($this->candidateRoutes) > 1) {
380 | foreach ($this->candidateRoutes as $n => $rota) {
381 | $padrao = $rota->getPattern();
382 | if (preg_match('/\[:options\]/', $padrao)) {
383 | unset($this->candidateRoutes[$n]);
384 | }
385 | }
386 | }
387 |
388 | foreach ($this->candidateRoutes as $n => $rota) {
389 | $padrao = $rota->getPattern();
390 | if (preg_match('/\[:options\]/', $padrao)) {
391 | $this->matchedRoute = $rota;
392 | $this->candidateRoutes = [];
393 | return;
394 | }
395 |
396 | $naoVariaveis = $this->getNonVariables($padrao);
397 |
398 | foreach ($naoVariaveis as $i => $valor) {
399 | if(!isset($similaridades[$n]))
400 | $similaridades[$n] = 0;
401 |
402 | if (isset($expUri[$i]) && $expUri[$i] == $valor) {
403 | $similaridades[$n] += 1;
404 | }
405 | }
406 | }
407 |
408 | $bigger = max(array_values($similaridades));
409 | $mostSimilar = array_search($bigger, $similaridades);
410 | $this->matchedRoute = $this->candidateRoutes[$mostSimilar];
411 | $this->candidateRoutes = [];
412 | }
413 |
414 | /**
415 | * Retorna a rota que coincidiu com a RequestUri atual
416 | * @return DRouter\Route
417 | */
418 | public function getMatchedRoute()
419 | {
420 | return $this->matchedRoute;
421 | }
422 |
423 | /**
424 | * Agrupa um array de middlewares com outro array de middlewares
425 | * UTilizado para a junção de middlewares globais com middlewares de rota
426 | * @param array $middlewares1
427 | * @param array $middlewares2
428 | * @return array
429 | */
430 | private function joinMiddlewares(array $middlewares1, array $middlewares2)
431 | {
432 | return array_merge($middlewares1, $middlewares2);
433 | }
434 |
435 |
436 | /**
437 | * Executa callable da rota que coincidiu
438 | * passando como ultimo prametro o objeto container, caso necessário
439 | * @param $container DRouter\Container
440 | */
441 | public function execute(\Drouter\Container $container)
442 | {
443 | $rota = $this->getMatchedRoute();
444 |
445 | $callable = $rota->getCallable();
446 | $params = $rota->getParams();
447 |
448 | $middlewares = $this->getMiddlewares();
449 | $routeName = $rota->getName();
450 | $groupPrefix = $rota->getGroupPrefix();
451 |
452 | //Middlewares de group
453 | if (!is_null($groupPrefix) && isset($this->groupMiddlewares[$groupPrefix])) {
454 | $middlewares1 = $this->groupMiddlewares[$groupPrefix];
455 | $routeMiddlewares = $rota->getMiddlewares();
456 | $rota->addMiddlewares($this->joinMiddlewares($middlewares1, $routeMiddlewares));
457 | }
458 |
459 | //Middlewares de routeNames armazenadas em memoria para adição posterior
460 | if (isset($middlewares[$routeName])) {
461 | $middlewares1 = $middlewares[$routeName];
462 | $routeMiddlewares = $rota->getMiddlewares();
463 | $rota->addMiddlewares($this->joinMiddlewares($middlewares1, $routeMiddlewares));
464 | unset($this->middlewares[$routeName]);
465 | }
466 |
467 | //rotas globais acima das indicadas com name
468 | if (isset($middlewares['*'])) {
469 | $middlewares1 = $middlewares['*'];
470 | $routeMiddlewares = $rota->getMiddlewares();
471 | $rota->addMiddlewares($this->joinMiddlewares($middlewares1, $routeMiddlewares));
472 | }
473 |
474 | if (count($rota->getMiddlewares())) {
475 | Middleware::executeMiddlewares($rota->getMiddlewares(), $container);
476 | }
477 |
478 | if (is_string($callable) && preg_match('/^[a-zA-Z\d\\\\]+[\:][\w\d]+$/', $callable)) {
479 | $exp = explode(':', $callable);
480 |
481 | $obj = filter_var($exp[0], FILTER_UNSAFE_RAW);
482 | $obj = new $obj($container);
483 | $method = filter_var($exp[1], FILTER_UNSAFE_RAW);
484 |
485 | $callable = [$obj, $method];
486 | }
487 |
488 |
489 | if (!empty($params)) {
490 | $params = array_values($params);
491 | }
492 |
493 | $container->options = $rota->getOptions();
494 |
495 | if (is_object($callable) || (is_string($callable) && is_callable($callable))) {
496 | $params[] = $container;
497 | }
498 |
499 | call_user_func_array($callable, $params);
500 | }
501 | }
502 |
--------------------------------------------------------------------------------