├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── examples ├── example.htaccess └── example.php ├── src ├── Router │ ├── Route.php │ ├── RouteNotFoundException.php │ └── Router.php └── php-5.3-support.php └── tests └── RouterTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | composer.lock 2 | vendor/ 3 | docs/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: php 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | install: 9 | - composer install 10 | script: 11 | - make tests 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Emilio Cobos Álvarez 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PHPCS := ./vendor/squizlabs/php_codesniffer/scripts/phpcs 2 | PHPCFB := ./vendor/squizlabs/php_codesniffer/scripts/phpcbf 3 | PHPUNIT := ./vendor/phpunit/phpunit/phpunit 4 | PHPDOC := ./vendor/phpdocumentor/phpdocumentor/bin/phpdoc 5 | PHP_STANDARD ?= PSR2 6 | 7 | .PHONY: tests 8 | tests: 9 | $(PHPCS) --standard=$(PHP_STANDARD) src tests 10 | $(PHPUNIT) -v --bootstrap vendor/autoload.php tests 11 | 12 | .PHONY: autofix 13 | autofix: 14 | $(PHPCFB) --standard=$(PHP_STANDARD) src tests 15 | 16 | docs: 17 | $(PHPDOC) -d src -t docs 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Router 2 | 3 | [![Build Status](https://travis-ci.org/ecoal95/php-router.svg)](https://travis-ci.org/ecoal95/php-router) 4 | [![Latest Stable Version](https://poser.pugx.org/ecoal95/php-router/v/stable)](https://packagist.org/packages/ecoal95/php-router) 5 | [![Total Downloads](https://poser.pugx.org/ecoal95/php-router/downloads)](https://packagist.org/packages/ecoal95/php-router) 6 | [![Latest Unstable Version](https://poser.pugx.org/ecoal95/php-router/v/unstable)](https://packagist.org/packages/ecoal95/php-router) 7 | [![License](https://poser.pugx.org/ecoal95/php-router/license)](https://packagist.org/packages/ecoal95/php-router) 8 | 9 | ## Contributors 10 | * [Emilio Cobos](https://github.com/ecoal95) 11 | * [Bryan Velastegui](https://github.com/shinigamicorei7) 12 | * [Soviut](https://github.com/Soviut) 13 | 14 | ## Composer setup example 15 | ```json 16 | { 17 | "require": { 18 | "ecoal95/php-router": "dev-master" 19 | } 20 | } 21 | ``` 22 | 23 | ## Usage 24 | See the `examples/` to see a basic example and *remember to use the htaccess provided there*! 25 | 26 | ### Get urls 27 | ```html 28 | Username's profile 29 | ``` 30 | 31 | #### Get the current url 32 | ```php 33 | $router->url(); 34 | ``` 35 | 36 | ### Blog example 37 | ```php 38 | $router = new Router\Router('/blog'); 39 | 40 | $router->add('/', function() { 41 | echo 'homepage'; 42 | }); 43 | 44 | $router->add('/about', function() { 45 | echo 'about page'; 46 | }); 47 | 48 | $router->add('/([0-9]{4})', function($year) { 49 | echo $year . ' years active'; 50 | }); 51 | 52 | $router->add('/([0-9]{4})/([0-9]{2})', function ($year, $month) { 53 | echo 'archives from ' . $year . '/' . $month; 54 | }); 55 | 56 | $router->add('/(.*)', function($slug) { 57 | // For example: 58 | if( Article::where('slug', '=', $slug)->first() ) { 59 | echo 'article'; 60 | } else { 61 | echo '404'; 62 | } 63 | }); 64 | 65 | $router->post('/posts/create', function() { 66 | echo 'post created'; 67 | }); 68 | 69 | $router->post('/posts/update/([0-9]+)', function($post_id){ 70 | // Update post with id = $post_id 71 | }); 72 | 73 | // compare url with all registered routes 74 | $router->route(); 75 | ``` 76 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecoal95/php-router", 3 | "type": "library", 4 | "description": "Minimal routing library", 5 | "keywords": ["route", "routing", "router"], 6 | "homepage": "https://github.com/ecoal95/php-router", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Emilio Cobos Álvarez", 11 | "email": "emiliocobos@usal.es", 12 | "homepage": "http://emiliocobos.net", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=5.3.0" 18 | }, 19 | "autoload": { 20 | "psr-0": { 21 | "Router": "src" 22 | }, 23 | "files" : [ 24 | "src/php-5.3-support.php" 25 | ] 26 | }, 27 | "require-dev": { 28 | "squizlabs/php_codesniffer": "2.*", 29 | "phpunit/phpunit": "4.8.*", 30 | "phpdocumentor/phpdocumentor": "2.8.*", 31 | "ext-xdebug": "*" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/example.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine on 3 | 4 | RewriteCond %{REQUEST_FILENAME} !-f 5 | RewriteCond %{REQUEST_FILENAME} !-d 6 | 7 | RewriteRule ^(.*)$ index.php/$1 [L] 8 | -------------------------------------------------------------------------------- /examples/example.php: -------------------------------------------------------------------------------- 1 | add('/hola/([a-zA-Z]+)', function ($name) { 7 | echo sprintf('

Hola %s

', $name); 8 | }); 9 | 10 | $router->add('/.*', function () { 11 | header($_SERVER["SERVER_PROTOCOL"] . " 404 Not Found"); 12 | echo '

404 - El sitio solicitado no existe

'; 13 | }); 14 | 15 | $router->route(); 16 | -------------------------------------------------------------------------------- /src/Router/Route.php: -------------------------------------------------------------------------------- 1 | expr = '#^' . $expr . '/?$#'; 32 | $this->callback = $callback; 33 | 34 | if ($methods !== null) { 35 | $this->methods = is_array($methods) ? $methods : array($methods); 36 | } 37 | } 38 | 39 | /** 40 | * See if route matches with path 41 | * 42 | * @param string $path 43 | * @return boolean 44 | */ 45 | public function matches($path) 46 | { 47 | if (preg_match($this->expr, $path, $this->matches) && 48 | in_array($_SERVER['REQUEST_METHOD'], $this->methods)) { 49 | return true; 50 | } 51 | 52 | return false; 53 | } 54 | 55 | /** 56 | * Execute the callback. 57 | * The matches function needs to be called before this and return true. 58 | * We don't take the first match since it's the whole path 59 | */ 60 | public function exec() 61 | { 62 | return call_user_func_array($this->callback, array_slice($this->matches, 1)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Router/RouteNotFoundException.php: -------------------------------------------------------------------------------- 1 | code}]: {$this->message}\n"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Router/Router.php: -------------------------------------------------------------------------------- 1 | base_path = $base_path; 28 | $path = urldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH)); 29 | $path = substr($path, strlen($base_path)); 30 | $this->path = $path; 31 | } 32 | 33 | /** 34 | * Add a route 35 | * 36 | * @param string $expr 37 | * @param callable $callback 38 | * @param array|string $methods 39 | * @return void 40 | */ 41 | public function all($expr, $callback, $methods = null) 42 | { 43 | $this->routes[] = new Route($expr, $callback, $methods); 44 | } 45 | 46 | /** 47 | * Alias for all 48 | * 49 | * @param string $expr 50 | * @param callable $callback 51 | * @param null|array $methods 52 | */ 53 | public function add($expr, $callback, $methods = null) 54 | { 55 | $this->all($expr, $callback, $methods); 56 | } 57 | 58 | /** 59 | * Add a route for GET requests 60 | * 61 | * @param string $expr 62 | * @param callable $callback 63 | */ 64 | public function get($expr, $callback) 65 | { 66 | $this->routes[] = new Route($expr, $callback, 'GET'); 67 | } 68 | 69 | /** 70 | * Add a route for POST requests 71 | * 72 | * @param string $expr 73 | * @param callable $callback 74 | */ 75 | public function post($expr, $callback) 76 | { 77 | $this->routes[] = new Route($expr, $callback, 'POST'); 78 | } 79 | 80 | /** 81 | * Add a route for HEAD requests 82 | * 83 | * @param string $expr 84 | * @param callable $callback 85 | */ 86 | public function head($expr, $callback) 87 | { 88 | $this->routes[] = new Route($expr, $callback, 'HEAD'); 89 | } 90 | 91 | /** 92 | * Add a route for PUT requests 93 | * 94 | * @param string $expr 95 | * @param callable $callback 96 | */ 97 | public function put($expr, $callback) 98 | { 99 | $this->routes[] = new Route($expr, $callback, 'PUT'); 100 | } 101 | 102 | /** 103 | * Add a route for DELETE requests 104 | * 105 | * @param string $expr 106 | * @param callable $callback 107 | */ 108 | public function delete($expr, $callback) 109 | { 110 | $this->routes[] = new Route($expr, $callback, 'DELETE'); 111 | } 112 | 113 | /** 114 | * Test all routes until any of them matches 115 | * 116 | * @throws RouteNotFoundException if the route doesn't match with any of the registered routes 117 | */ 118 | public function route() 119 | { 120 | foreach ($this->routes as $route) { 121 | if ($route->matches($this->path)) { 122 | return $route->exec(); 123 | } 124 | } 125 | 126 | throw new RouteNotFoundException("No routes matching {$this->path}"); 127 | } 128 | 129 | /** 130 | * Get the current url or the url to a path 131 | * 132 | * @param string $path 133 | * @return string 134 | */ 135 | public function url($path = null) 136 | { 137 | if ($path === null) { 138 | $path = $this->path; 139 | } 140 | 141 | return $this->base_path . $path; 142 | } 143 | 144 | /** 145 | * Redirect from one url to another 146 | * 147 | * @param string $from_path 148 | * @param string $to_path 149 | * @param int $code 150 | */ 151 | public function redirect($from_path, $to_path, $code = 302) 152 | { 153 | $this->all($from_path, function () use ($to_path, $code) { 154 | http_response_code($code); 155 | header("Location: {$to_path}"); 156 | }); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/php-5.3-support.php: -------------------------------------------------------------------------------- 1 | all('/test_not_found', function () { 18 | 19 | }); 20 | 21 | try { 22 | $router->route(); 23 | } catch (\Exception $e) { 24 | $this->assertTrue($e instanceof RouteNotFoundException); 25 | } 26 | } 27 | 28 | public function testRouteExample() 29 | { 30 | $_SERVER['REQUEST_URI'] = '/hello'; 31 | $_SERVER['REQUEST_METHOD'] = 'GET'; 32 | 33 | $this->expectOutputString('pass'); 34 | 35 | $router = new Router(); 36 | 37 | $router->all('/hello', function () { 38 | echo 'pass'; 39 | }); 40 | 41 | try { 42 | $router->route(); 43 | } catch (RouteNotFoundException $e) { 44 | $this->assertTrue(false); 45 | } 46 | } 47 | 48 | public function testRouteParam() 49 | { 50 | $_SERVER['REQUEST_URI'] = '/post/123'; 51 | $_SERVER['REQUEST_METHOD'] = 'GET'; 52 | 53 | $this->expectOutputString('pass 123'); 54 | 55 | $router = new Router(); 56 | $router->all('/post/([0-9]+)', function ($param) { 57 | echo "pass {$param}"; 58 | }); 59 | 60 | try { 61 | $router->route(); 62 | } catch (RouteNotFoundException $e) { 63 | $this->assertTrue(false); 64 | } 65 | } 66 | 67 | /** 68 | * @runInSeparateProcess 69 | */ 70 | public function testRedirect() 71 | { 72 | $_SERVER['REQUEST_URI'] = '/redirect_test'; 73 | $_SERVER['REQUEST_METHOD'] = 'GET'; 74 | 75 | $router = new Router(); 76 | 77 | $router->redirect('/redirect_test', '/redirected', 301); 78 | 79 | $router->route(); 80 | 81 | $this->assertTrue(http_response_code() === 301); 82 | 83 | $headers = xdebug_get_headers(); 84 | 85 | $location_header_found = false; 86 | foreach ($headers as $header) { 87 | if (strpos($header, 'Location:') !== false) { 88 | $location_header_found = true; 89 | $this->assertTrue(strpos($header, '/redirected') !== false); 90 | } 91 | } 92 | 93 | $this->assertTrue($location_header_found); 94 | } 95 | } 96 | --------------------------------------------------------------------------------