diff --git a/README.md b/README.md index 5f06593..22ad391 100644 --- a/README.md +++ b/README.md @@ -158,11 +158,11 @@ namespace Demo\Handlers; use Pecee\Handlers\IExceptionHandler; use Pecee\Http\Request; use Pecee\SimpleRouter\Exceptions\NotFoundHttpException; -use Pecee\SimpleRouter\RouterEntry; +use Pecee\SimpleRouter\Route\ILoadableRoute; class CustomExceptionHandler implements IExceptionHandler { - public function handleError(Request $request, RouterEntry &$route = null, \Exception $error) + public function handleError(Request $request, ILoadableRoute &$route = null, \Exception $error) { /* You can use the exception handler to format errors depending on the request and type. */ @@ -214,18 +214,19 @@ Route::group(['domain' => '{account}.myapp.com'], function () { The prefix group array attribute may be used to prefix each route in the group with a given URI. For example, you may want to prefix all route URIs within the group with admin: -### Doing it the object oriented (hardcore) way +### Adding routes manually (with no helper) -The ```SimpleRouter``` class referenced in the previous example, is just a simple helper class that knows how to communicate with the ```RouterBase``` class. +The ```SimpleRouter``` class referenced in the previous example, is just a simple helper class that knows how to communicate with the ```Router``` class. If you are up for a challenge, want the full control or simply just want to create your own ```Router``` helper class, this example is for you. ```php -use \Pecee\SimpleRouter\RouterBase; -use \Pecee\SimpleRouter\RouterRoute; +use \Pecee\SimpleRouter\Router; +use \Pecee\SimpleRouter\Route\RouteUrl; -$router = RouterBase::getInstance(); +/* Grap the router instance */ +$router = Router::getInstance(); -$route = new RouterRoute('/answer/1', function() { +$route = new RouteUrl('/answer/1', function() { die('this callback will match /answer/1'); @@ -239,9 +240,11 @@ $route->setPrefix('v1'); $router->addRoute($route); ``` +### Extending + This is a simple example of an integration into a framework. -The framework has it's own ```Router``` class which inherits from the ```SimpleRouter``` class. This allows the framework to add custom functionality. +The framework has it's own ```Router``` class which inherits from the ```SimpleRouter``` class. This allows the framework to add custom functionality like loading a custom `routes.php` file or add debugging information etc. ```php namespace Demo; @@ -330,22 +333,73 @@ function input() { ## Getting urls -**In ```routes.php``` we have added this route:** +By default all controller and resource routes will use a simplified version of their url as name. + +**Get routes using custom name (single route)** ```php -SimpleRouter::get('/item/{id}', 'myController@show', ['as' => 'item']); +SimpleRouter::get('/product-view/{id}', 'ProductsController@show', ['as' => 'product']); + +url('product', ['id' => 22], ['category' => 'shoes']); + +# output +# /product-view/22/?category=shoes ``` -**In the template we then call:** +**Getting the url using the name (controller route)** ```php -url('item', ['id' => 22], ['category' => 'shoes']); +SimpleRouter::controller('/images', 'ImagesController', ['as' => 'picture']); + +url('picture@getView', null, ['category' => 'shoes']); +url('picture', 'getView', ['category' => 'shoes']); + +# output +# /images/view/?category=shows +# /images/view/?category=shows ``` -**Result url is:** +**Getting the url using class** ```php -/item/22/?category=shoes +SimpleRouter::get('/product-view/{id}', 'ProductsController@show', ['as' => 'product']); +SimpleRouter::controller('/images', 'ImagesController'); + +url('ProductsController@show', ['id' => 22], ['category' => 'shoes']); +url('ImagesController@getImage', null, ['id' => 22]); + +# output +# /product-view/22/?category=shoes +# /images/image/?category=shows +``` + +**Using custom names for methods on a controller/resource route** + +```php +SimpleRouter::controller('gadgets', 'GadgetsController', ['names' => ['getIphoneInfo' => 'iphone']]); + +url('gadgets.iphone'); + +# output +# /gadgets/iphoneinfo/ +``` + +**Getting REST/resource controller urls** + +```php +SimpleRouter::resource('/phones', 'PhonesController'); + +url('phones'); +url('phones.index'); +url('phones.create'); +url('phones.edit'); + +// etc.. + +# output +# /phones/ +# /phones/create/ +# /phones/edit/ ``` ## Custom CSRF verifier @@ -379,13 +433,13 @@ SimpleRouter::csrfVerifier(new \Demo\Middleware\CsrfVerifier()); Sometimes it can be necessary to keep urls stored in the database, file or similar. In this example, we want the url ```/my-cat-is-beatiful``` to load the route ```/article/view/1``` which the router knows, because it's defined in the ```routes.php``` file. -To interfere with the router, we create a class that inherits from ```RouterBootManager```. This class will be loaded before any other rules in ```routes.php``` and allow us to "change" the current route, if any of our criteria are fulfilled (like coming from the url ```/my-cat-is-beatiful```). +To interfere with the router, we create a class that implements the ```IRouterBootManager``` interface. This class will be loaded before any other rules in ```routes.php``` and allow us to "change" the current route, if any of our criteria are fulfilled (like coming from the url ```/my-cat-is-beatiful```). ```php use Pecee\Http\Request; -use Pecee\SimpleRouter\RouterBootManager; +use Pecee\SimpleRouter\IRouterBootManager; -class CustomRouterRules extends RouterBootManager { +class CustomRouterRules implement IRouterBootManager { public function boot(Request $request) { @@ -421,7 +475,7 @@ The last thing we need to do, is to add our custom boot-manager to the ```routes ## Easily overwrite route about to be loaded Sometimes it can be useful to manipulate the route about to be loaded. simple-php-router allows you to easily change the route about to be executed. -All information about the current route is stored in the ```\Pecee\SimpleRouter\RouterBase``` instance's `loadedRoute` property. +All information about the current route is stored in the ```\Pecee\SimpleRouter\Router``` instance's `loadedRoute` property. For easy access you can use the shortcut method `\Pecee\SimpleRouter\SimpleRouter::router()`. @@ -442,7 +496,7 @@ $route->setMethod('hello'); ### Examples It's only possible to change the route BEFORE the route has initially been loaded. If you want to redirect to another route, we highly recommend that you -modify the `RouterEntry` object from a `Middleware` or `ExceptionHandler`, like the examples below. +modify the `IRoute` object from a `Middleware` or `ExceptionHandler`, like the examples below. #### Rewriting to new route @@ -456,11 +510,11 @@ namespace Demo\Middlewares; use Pecee\Http\Middleware\IMiddleware; use Pecee\Http\Request; -use Pecee\SimpleRouter\RouterEntry; +use Pecee\SimpleRouter\Route\ILoadableRoute; class CustomMiddleware implements Middleware { - public function handle(Request $request, RouterEntry &$route) { + public function handle(Request $request, ILoadableRoute &$route) { $request->setUri(url('home')); @@ -484,11 +538,11 @@ namespace Demo\Middlewares; use Pecee\Http\Middleware\IMiddleware; use Pecee\Http\Request; -use Pecee\SimpleRouter\RouterEntry; +use Pecee\SimpleRouter\Route\ILoadableRoute; class CustomMiddleware implements Middleware { - public function handle(Request $request, RouterEntry &$route) { + public function handle(Request $request, ILoadableRoute &$route) { $route->callback('DefaultController@home'); diff --git a/demo-project/app/Handlers/CustomExceptionHandler.php b/demo-project/app/Handlers/CustomExceptionHandler.php index 10a67f1..6eca147 100644 --- a/demo-project/app/Handlers/CustomExceptionHandler.php +++ b/demo-project/app/Handlers/CustomExceptionHandler.php @@ -4,11 +4,11 @@ namespace Demo\Handlers; use Pecee\Handlers\IExceptionHandler; use Pecee\Http\Request; use Pecee\SimpleRouter\Exceptions\NotFoundHttpException; -use Pecee\SimpleRouter\RouterEntry; +use Pecee\SimpleRouter\Route\ILoadableRoute; class CustomExceptionHandler implements IExceptionHandler { - public function handleError(Request $request, RouterEntry &$route = null, \Exception $error) + public function handleError(Request $request, ILoadableRoute &$route = null, \Exception $error) { /* You can use the exception handler to format errors depending on the request and type. */ diff --git a/demo-project/app/Middlewares/ApiVerification.php b/demo-project/app/Middlewares/ApiVerification.php index 308bc29..ce9e177 100644 --- a/demo-project/app/Middlewares/ApiVerification.php +++ b/demo-project/app/Middlewares/ApiVerification.php @@ -3,11 +3,11 @@ namespace Demo\Middlewares; use Pecee\Http\Middleware\IMiddleware; use Pecee\Http\Request; -use Pecee\SimpleRouter\RouterEntry; +use Pecee\SimpleRouter\Route\ILoadableRoute; class ApiVerification implements IMiddleware { - public function handle(Request $request, RouterEntry &$route) + public function handle(Request $request, ILoadableRoute &$route) { // Do authentication $request->authenticated = true; diff --git a/src/Pecee/Handlers/IExceptionHandler.php b/src/Pecee/Handlers/IExceptionHandler.php index 1167e1a..fa2561e 100644 --- a/src/Pecee/Handlers/IExceptionHandler.php +++ b/src/Pecee/Handlers/IExceptionHandler.php @@ -2,16 +2,16 @@ namespace Pecee\Handlers; use Pecee\Http\Request; -use Pecee\SimpleRouter\RouterEntry; +use Pecee\SimpleRouter\Route\ILoadableRoute; interface IExceptionHandler { /** * @param Request $request - * @param RouterEntry|null $route + * @param ILoadableRoute $route * @param \Exception $error * @return Request|null */ - public function handleError(Request $request, RouterEntry &$route = null, \Exception $error); + public function handleError(Request $request, ILoadableRoute &$route = null, \Exception $error); } \ No newline at end of file diff --git a/src/Pecee/Http/Middleware/BaseCsrfVerifier.php b/src/Pecee/Http/Middleware/BaseCsrfVerifier.php index 263d07c..ed96b69 100644 --- a/src/Pecee/Http/Middleware/BaseCsrfVerifier.php +++ b/src/Pecee/Http/Middleware/BaseCsrfVerifier.php @@ -4,7 +4,7 @@ namespace Pecee\Http\Middleware; use Pecee\CsrfToken; use Pecee\Exceptions\TokenMismatchException; use Pecee\Http\Request; -use Pecee\SimpleRouter\RouterEntry; +use Pecee\SimpleRouter\Route\ILoadableRoute; class BaseCsrfVerifier implements IMiddleware { @@ -51,7 +51,7 @@ class BaseCsrfVerifier implements IMiddleware return false; } - public function handle(Request $request, RouterEntry &$route = null) + public function handle(Request $request, ILoadableRoute &$route = null) { if ($request->getMethod() !== 'get' && !$this->skip($request)) { diff --git a/src/Pecee/Http/Middleware/IMiddleware.php b/src/Pecee/Http/Middleware/IMiddleware.php index 454c487..bbd5103 100644 --- a/src/Pecee/Http/Middleware/IMiddleware.php +++ b/src/Pecee/Http/Middleware/IMiddleware.php @@ -2,15 +2,15 @@ namespace Pecee\Http\Middleware; use Pecee\Http\Request; -use Pecee\SimpleRouter\RouterEntry; +use Pecee\SimpleRouter\Route\ILoadableRoute; interface IMiddleware { /** * @param Request $request - * @param RouterEntry $route + * @param ILoadableRoute $route * @return Request|null */ - public function handle(Request $request, RouterEntry &$route); + public function handle(Request $request, ILoadableRoute &$route); } \ No newline at end of file diff --git a/src/Pecee/SimpleRouter/ILoadableRoute.php b/src/Pecee/SimpleRouter/ILoadableRoute.php deleted file mode 100644 index 1cb071e..0000000 --- a/src/Pecee/SimpleRouter/ILoadableRoute.php +++ /dev/null @@ -1,9 +0,0 @@ -url; - } - - /** - * Set url - * - * @param string $url - * @return static - */ - public function setUrl($url) - { - $this->url = ($url === '/') ? '/' : '/' . trim($url, '/') . '/'; - $regex = sprintf(static::PARAMETERS_REGEX_MATCH, static::PARAMETER_MODIFIERS[0], static::PARAMETER_OPTIONAL_SYMBOL, static::PARAMETER_MODIFIERS[1]); - - if (preg_match_all('/' . $regex . '/is', $this->url, $matches)) { - foreach ($matches[1] as $key) { - $this->parameters[$key] = null; - } - } - - return $this; - } - - /** - * Returns the provided name of the router (first if multiple). - * Alias for LoadableRoute::getName(). - * - * @see LoadableRoute::getName() - * @return string|array - */ - public function getAlias() - { - return $this->getName(); - } - - /** - * Returns the provided name for the router (first if multiple). - * @return string - */ - public function getName() - { - return $this->names[0]; - } - - /** - * Get route names - * @return array - */ - public function getNames() - { - return $this->names; - } - - /** - * Check if route has given name. - * Alias for LoadableRoute::hasName(); - * - * @see LoadableRoute::hasName() - * @param $name - */ - public function hasAlias($name) - { - $this->hasName($name); - } - - /** - * Check if route has given name. - * - * @param string $name - * @return bool - */ - public function hasName($name) - { - return (in_array($name, $this->names, false) !== false); - } - - /** - * Sets the router name, which makes it easier to obtain the url or router at a later point. - * Alias for LoadableRoute::setName(). - * - * @see LoadableRoute::setName() - * @param string|array $name - * @return static - */ - public function setAlias($name) - { - return $this->setName($name); - } - - /** - * Sets the router name, which makes it easier to obtain the url or router at a later point. - * - * @param string $name - * @return static $this - */ - public function setName($name) - { - array_push($this->names, $name); - - return $this; - } - - /** - * Set multiple names for the route - * - * @param array $names - * @return static $this - */ - public function setNames(array $names) - { - $this->names = $names; - - return $this; - } - - /** - * Merge with information from another route. - * - * @param array $values - * @return static - */ - public function setSettings(array $values) - { - if (isset($values['as'])) { - $this->setNames((array)$values['as']); - } - - if (isset($values['prefix'])) { - $this->setUrl($values['prefix'] . $this->getUrl()); - } - - parent::setSettings($values); - - return $this; - } - -} \ No newline at end of file diff --git a/src/Pecee/SimpleRouter/IControllerRoute.php b/src/Pecee/SimpleRouter/Route/IControllerRoute.php similarity index 67% rename from src/Pecee/SimpleRouter/IControllerRoute.php rename to src/Pecee/SimpleRouter/Route/IControllerRoute.php index 242deeb..92fa494 100644 --- a/src/Pecee/SimpleRouter/IControllerRoute.php +++ b/src/Pecee/SimpleRouter/Route/IControllerRoute.php @@ -1,7 +1,7 @@ getMiddlewares()) > 0) { + foreach ($this->getMiddlewares() as $middleware) { + + $middleware = $this->loadClass($middleware); + if (!($middleware instanceof IMiddleware)) { + throw new HttpException($middleware . ' must be instance of Middleware'); + } + + $middleware->handle($request, $route); + } + } + } + + /** + * Set url + * + * @param string $url + * @return static + */ + public function setUrl($url) + { + $this->url = ($url === '/') ? '/' : '/' . trim($url, '/') . '/'; + $regex = sprintf(static::PARAMETERS_REGEX_MATCH, $this->paramModifiers[0], $this->paramOptionalSymbol, $this->paramModifiers[1]); + + if (preg_match_all('/' . $regex . '/is', $this->url, $matches)) { + foreach ($matches[1] as $key) { + $this->parameters[$key] = null; + } + } + + return $this; + } + + public function getUrl() + { + return $this->url; + } + + public function findUrl($method = null, $parameters = null, $name = null) + { + $url = ''; + + $parameters = (array)$parameters; + + if ($this->getGroup() !== null && count($this->getGroup()->getDomains()) > 0) { + $url .= '//' . $this->getGroup()->getDomains()[0]; + } + + $url .= $this->getUrl(); + + $params = array_merge($this->getParameters(), $parameters); + + /* Url that contains parameters that aren't recognized */ + $unknownParams = []; + + /* Create the param string - {} */ + $param1 = $this->paramModifiers[0] . '%s' . $this->paramModifiers[1]; + + /* Create the param string with the optional symbol - {?} */ + $param2 = $this->paramModifiers[0] . '%s' . $this->paramOptionalSymbol . $this->paramModifiers[1]; + + /* Let's parse the values of any {} parameter in the url */ + foreach ($params as $param => $value) { + $value = (isset($parameters[$param])) ? $parameters[$param] : $value; + + if (stripos($url, $param1) !== false || stripos($url, $param) !== false) { + $url = str_ireplace([sprintf($param1, $param), sprintf($param2, $param)], $value, $url); + } else { + $unknownParams[$param] = $value; + } + } + + $url .= '/' . join('/', $unknownParams); + + return rtrim($url, '/') . '/'; + } + + /** + * Returns the provided name for the router (first if multiple). + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * Check if route has given name. + * + * @param string $name + * @return bool + */ + public function hasName($name) + { + return (strtolower($this->name) === strtolower($name)); + } + + /** + * Sets the router name, which makes it easier to obtain the url or router at a later point. + * Alias for LoadableRoute::setName(). + * + * @see LoadableRoute::setName() + * @param string|array $name + * @return static + */ + public function name($name) + { + return $this->setName($name); + } + + /** + * Sets the router name, which makes it easier to obtain the url or router at a later point. + * + * @param string $name + * @return static $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * Export route settings to array so they can be merged with another route. + * + * @return array + */ + public function toArray() + { + $values = []; + + if (count($this->middlewares) > 0) { + $values['middleware'] = $this->middlewares; + } + + return array_merge(parent::toArray(), $values); + } + + /** + * Merge with information from another route. + * + * @param array $values + * @param bool $merge + * @return static + */ + public function setSettings(array $values, $merge = false) + { + if (isset($values['as'])) { + if ($this->name !== null && $merge !== false) { + $this->setName($values['as'] . '.' . $this->name); + } else { + $this->setName($values['as']); + } + } + + if (isset($values['prefix'])) { + $this->setUrl($values['prefix'] . $this->getUrl()); + } + + // Push middleware if multiple + if (isset($values['middleware'])) { + $this->setMiddlewares(array_merge((array)$values['middleware'], $this->middlewares)); + } + + parent::setSettings($values, $merge); + + return $this; + } + + /** + * @param string $middleware + * @return static + */ + public function setMiddleware($middleware) + { + $this->middlewares[] = $middleware; + + return $this; + } + + public function setMiddlewares(array $middlewares) + { + $this->middlewares = $middlewares; + + return $this; + } + + /** + * @return string|array + */ + public function getMiddlewares() + { + return $this->middlewares; + } + +} \ No newline at end of file diff --git a/src/Pecee/SimpleRouter/RouterEntry.php b/src/Pecee/SimpleRouter/Route/Route.php similarity index 85% rename from src/Pecee/SimpleRouter/RouterEntry.php rename to src/Pecee/SimpleRouter/Route/Route.php index f61022a..92efae2 100644 --- a/src/Pecee/SimpleRouter/RouterEntry.php +++ b/src/Pecee/SimpleRouter/Route/Route.php @@ -1,12 +1,11 @@ getCallback() !== null && is_callable($this->getCallback())) { + + /* When the callback is a function */ + call_user_func_array($this->getCallback(), $this->getParameters()); + + } else { + + /* When the callback is a method */ + $controller = explode('@', $this->getCallback()); + $className = $this->getNamespace() . '\\' . $controller[0]; + + $class = $this->loadClass($className); + $method = $controller[1]; + + if (!method_exists($class, $method)) { + throw new NotFoundHttpException(sprintf('Method %s does not exist in class %s', $method, $className), 404); + } + + $parameters = array_filter($this->getParameters(), function ($var) { + return ($var !== null); + }); + + call_user_func_array([$class, $method], $parameters); + + return $class; } - return new $name(); + return null; } protected function parseParameters($route, $url, $parameterRegex = '[\w]+') @@ -125,51 +148,13 @@ abstract class RouterEntry implements IRoute return null; } - public function loadMiddleware(Request $request, LoadableRoute &$route) + protected function loadClass($name) { - if (count($this->getMiddlewares()) > 0) { - foreach ($this->getMiddlewares() as $middleware) { - - $middleware = $this->loadClass($middleware); - if (!($middleware instanceof IMiddleware)) { - throw new HttpException($middleware . ' must be instance of Middleware'); - } - - $middleware->handle($request, $route); - } - } - } - - public function renderRoute(Request $request) - { - if ($this->getCallback() !== null && is_callable($this->getCallback())) { - - /* When the callback is a function */ - call_user_func_array($this->getCallback(), $this->getParameters()); - - } else { - - /* When the callback is a method */ - $controller = explode('@', $this->getCallback()); - $className = $this->getNamespace() . '\\' . $controller[0]; - - $class = $this->loadClass($className); - $method = $controller[1]; - - if (!method_exists($class, $method)) { - throw new NotFoundHttpException(sprintf('Method %s does not exist in class %s', $method, $className), 404); - } - - $parameters = array_filter($this->getParameters(), function ($var) { - return ($var !== null); - }); - - call_user_func_array([$class, $method], $parameters); - - return $class; + if (!class_exists($name)) { + throw new HttpException(sprintf('Class %s does not exist', $name), 500); } - return null; + return new $name(); } /** @@ -221,7 +206,7 @@ abstract class RouterEntry implements IRoute /** * Get the group for the route. * - * @return RouterGroup|null + * @return IGroupRoute|null */ public function getGroup() { @@ -231,11 +216,14 @@ abstract class RouterEntry implements IRoute /** * Set group * - * @param RouterGroup $group + * @param IGroupRoute $group + * @return static $this */ - public function setGroup(RouterGroup $group) + public function setGroup(IGroupRoute $group) { $this->group = $group; + + return $this; } /** @@ -306,24 +294,6 @@ abstract class RouterEntry implements IRoute return $this; } - /** - * @param string $middleware - * @return static - */ - public function setMiddleware($middleware) - { - $this->middlewares[] = $middleware; - - return $this; - } - - public function setMiddlewares(array $middlewares) - { - $this->middlewares = $middlewares; - - return $this; - } - /** * @param string $namespace * @return static $this @@ -351,14 +321,6 @@ abstract class RouterEntry implements IRoute return $this->defaultNamespace; } - /** - * @return string|array - */ - public function getMiddlewares() - { - return $this->middlewares; - } - /** * @return string */ @@ -367,38 +329,6 @@ abstract class RouterEntry implements IRoute return ($this->namespace === null) ? $this->defaultNamespace : $this->namespace; } - /** - * @return array - */ - public function getParameters() - { - return $this->parameters; - } - - /** - * @param mixed $parameters - * @return static - */ - public function setParameters($parameters) - { - $this->parameters = $parameters; - - return $this; - } - - /** - * Add regular expression parameter match - * - * @param array $options - * @return static - */ - public function setWhere(array $options) - { - $this->where = $options; - - return $this; - } - /** * Add regular expression match for the entire route. * @@ -425,18 +355,14 @@ abstract class RouterEntry implements IRoute $values['namespace'] = $this->namespace; } - if (count($this->middlewares) > 0) { - $values['middleware'] = $this->middlewares; + if (count($this->requestMethods) > 0) { + $values['method'] = $this->requestMethods; } if (count($this->where) > 0) { $values['where'] = $this->where; } - if (count($this->requestMethods) > 0) { - $values['method'] = $this->requestMethods; - } - if (count($this->parameters) > 0) { $values['parameters'] = $this->parameters; } @@ -448,19 +374,15 @@ abstract class RouterEntry implements IRoute * Merge with information from another route. * * @param array $values + * @param bool $merge * @return static $this */ - public function setSettings(array $values) + public function setSettings(array $values, $merge = false) { if (isset($values['namespace']) && $this->namespace === null) { $this->setNamespace($values['namespace']); } - // Push middleware if multiple - if (isset($values['middleware'])) { - $this->setMiddlewares(array_merge((array)$values['middleware'], $this->middlewares)); - } - if (isset($values['method'])) { $this->setRequestMethods(array_merge($this->requestMethods, (array)$values['method'])); } @@ -476,4 +398,60 @@ abstract class RouterEntry implements IRoute return $this; } + /** + * Get parameter names. + * + * @return array + */ + public function getWhere() + { + return $this->where; + } + + /** + * Set parameter names. + * + * @param array $options + * @return static + */ + public function setWhere(array $options) + { + $this->where = $options; + + return $this; + } + + /** + * Add regular expression parameter match. + * Alias for LoadableRoute::where() + * + * @see LoadableRoute::where() + * @param array $options + * @return static + */ + public function where(array $options) { + return $this->where($options); + } + + /** + * Get parameters + * + * @return array + */ + public function getParameters() + { + return $this->parameters; + } + + /** + * Get parameters + * + * @param array $parameters + * @return static $this + */ + public function setParameters(array $parameters) { + $this->parameters = $parameters; + return $this; + } + } \ No newline at end of file diff --git a/src/Pecee/SimpleRouter/RouterController.php b/src/Pecee/SimpleRouter/Route/RouteController.php similarity index 54% rename from src/Pecee/SimpleRouter/RouterController.php rename to src/Pecee/SimpleRouter/Route/RouteController.php index 4ea9949..b2e9f12 100644 --- a/src/Pecee/SimpleRouter/RouterController.php +++ b/src/Pecee/SimpleRouter/Route/RouteController.php @@ -1,21 +1,82 @@ setUrl($url); + $this->setName(trim(str_replace('/', '.', $url), '/')); $this->controller = $controller; } + /** + * Check if route has given name. + * + * @param string $name + * @return bool + */ + public function hasName($name) + { + if ($this->name === null) { + return false; + } + + /* Remove method/type */ + if (stripos($name, '.') !== false) { + $method = substr($name, strrpos($name, '.') + 1); + $newName = substr($name, 0, strrpos($name, '.')); + + if (strtolower($this->name) === strtolower($newName) && in_array($method, $this->names)) { + return true; + } + } + + return parent::hasName($name); + } + + public function findUrl($method = null, $parameters = null, $name = null) + { + + if (stripos($name, '.') !== false) { + $found = array_search(substr($name, strrpos($name, '.') + 1), $this->names); + if ($found !== false) { + $method = $found; + } + } + + $url = ''; + + $parameters = (array)$parameters; + + /* Remove requestType from method-name, if it exists */ + if ($method !== null) { + foreach (static::$requestTypes as $requestType) { + if (stripos($method, $requestType) === 0) { + $method = substr($method, strlen($requestType)); + break; + } + } + $method .= '/'; + } + + if ($this->getGroup() !== null && count($this->getGroup()->getDomains()) > 0) { + $url .= '//' . $this->getGroup()->getDomains()[0]; + } + + $url .= '/' . trim($this->getUrl(), '/') . '/' . strtolower($method) . join('/', $parameters); + + return '/' . trim($url, '/') . '/'; + } + public function renderRoute(Request $request) { if ($this->getCallback() !== null && is_callable($this->getCallback())) { @@ -109,4 +170,22 @@ class RouterController extends LoadableRoute implements IControllerRoute return $this; } + /** + * Merge with information from another route. + * + * @param array $values + * @param bool $merge + * @return static + */ + public function setSettings(array $values, $merge = false) + { + if (isset($values['names'])) { + $this->names = $values['names']; + } + + parent::setSettings($values, $merge); + + return $this; + } + } \ No newline at end of file diff --git a/src/Pecee/SimpleRouter/RouterGroup.php b/src/Pecee/SimpleRouter/Route/RouteGroup.php similarity index 80% rename from src/Pecee/SimpleRouter/RouterGroup.php rename to src/Pecee/SimpleRouter/Route/RouteGroup.php index 1ede4f4..929e16e 100644 --- a/src/Pecee/SimpleRouter/RouterGroup.php +++ b/src/Pecee/SimpleRouter/Route/RouteGroup.php @@ -1,11 +1,12 @@ setPrefix($values['prefix'] . $this->prefix); } @@ -102,7 +105,15 @@ class RouterGroup extends RouterEntry $this->setDomains((array)$values['domain']); } - parent::setSettings($values); + if (isset($values['as'])) { + if ($this->name !== null && $merge !== false) { + $this->name = $values['as'] . '.' . $this->name; + } else { + $this->name = $values['as']; + } + } + + parent::setSettings($values, $merge); return $this; } @@ -120,6 +131,10 @@ class RouterGroup extends RouterEntry $values['prefix'] = $this->getPrefix(); } + if ($this->name !== null) { + $values['as'] = $this->name; + } + return array_merge($values, parent::toArray()); } diff --git a/src/Pecee/SimpleRouter/RouterResource.php b/src/Pecee/SimpleRouter/Route/RouteResource.php similarity index 62% rename from src/Pecee/SimpleRouter/RouterResource.php rename to src/Pecee/SimpleRouter/Route/RouteResource.php index 56ea3dc..9a0853e 100644 --- a/src/Pecee/SimpleRouter/RouterResource.php +++ b/src/Pecee/SimpleRouter/Route/RouteResource.php @@ -1,17 +1,62 @@ '', + 'create' => 'create', + 'store' => '', + 'show' => '', + 'edit' => 'edit', + 'update' => '', + 'destroy' => '', + ]; + protected $names = []; protected $controller; public function __construct($url, $controller) { $this->setUrl($url); $this->controller = $controller; + $this->setName(trim(str_replace('/', '.', $url), '/')); + } + + /** + * Check if route has given name. + * + * @param string $name + * @return bool + */ + public function hasName($name) + { + if ($this->name === null) { + return false; + } + + if (strtolower($this->name) === strtolower($name)) { + return true; + } + + /* Remove method/type */ + if (stripos($name, '.') !== false) { + $name = substr($name, 0, strrpos($name, '.')); + } + + return (strtolower($this->name) === strtolower($name)); + } + + public function findUrl($method = null, $parameters = null, $name = null) + { + $method = array_search($name, $this->names); + if ($method !== false) { + return rtrim($this->url . $this->urls[$method], '/') . '/'; + } + + return $this->url; } public function renderRoute(Request $request) @@ -120,4 +165,39 @@ class RouterResource extends LoadableRoute implements IControllerRoute return $this; } + public function setName($name) + { + $this->name = $name; + + $this->names = [ + 'index' => $this->name . '.index', + 'create' => $this->name . '.create', + 'store' => $this->name . '.store', + 'show' => $this->name . '.show', + 'edit' => $this->name . '.edit', + 'update' => $this->name . '.update', + 'destroy' => $this->name . '.destroy', + ]; + + return $this; + } + + /** + * Merge with information from another route. + * + * @param array $values + * @param bool $merge + * @return static + */ + public function setSettings(array $values, $merge = false) + { + if (isset($values['names'])) { + $this->names = $values['names']; + } + + parent::setSettings($values, $merge); + + return $this; + } + } \ No newline at end of file diff --git a/src/Pecee/SimpleRouter/RouterRoute.php b/src/Pecee/SimpleRouter/Route/RouteUrl.php similarity index 92% rename from src/Pecee/SimpleRouter/RouterRoute.php rename to src/Pecee/SimpleRouter/Route/RouteUrl.php index a11fb28..e7a73f6 100644 --- a/src/Pecee/SimpleRouter/RouterRoute.php +++ b/src/Pecee/SimpleRouter/Route/RouteUrl.php @@ -1,9 +1,9 @@ setParent($parent); /* Add/merge parent settings with child */ - $route->setSettings($parent->toArray()); + $route->setSettings($parent->toArray(), true); } @@ -204,7 +208,7 @@ class RouterBase /* Initialize boot-managers */ if (count($this->bootManagers) > 0) { - /* @var $manager RouterBootManager */ + /* @var $manager IRouterBootManager */ foreach ($this->bootManagers as $manager) { $this->request = $manager->boot($this->request); @@ -315,65 +319,15 @@ class RouterBase return ''; } - protected function processUrl(LoadableRoute $route, $method = null, $parameters = null, array $getParams = []) - { - $url = ''; - - $parameters = (array)$parameters; - - if ($route->getGroup() !== null && count($route->getGroup()->getDomains()) > 0) { - $url .= '//' . $route->getGroup()->getDomains()[0]; - } - - $url .= '/' . trim($route->getUrl(), '/'); - - if ($route instanceof IControllerRoute && $method !== null) { - - $url .= '/' . $method . '/'; - - if (count($parameters) > 0) { - $url .= join('/', $parameters); - } - - } else { - - $params = array_merge($route->getParameters(), $parameters); - - /* Url that contains parameters that aren't recognized */ - $unknownParams = []; - - /* Let's parse the values of any {} parameter in the url */ - foreach ($params as $param => $value) { - $value = (isset($parameters[$param])) ? $parameters[$param] : $value; - - /* Create the param string - {} */ - $param1 = LoadableRoute::PARAMETER_MODIFIERS[0] . $param . LoadableRoute::PARAMETER_MODIFIERS[1]; - - /* Create the param string with the optional symbol - {?} */ - $param2 = LoadableRoute::PARAMETER_MODIFIERS[0] . $param . LoadableRoute::PARAMETER_OPTIONAL_SYMBOL . LoadableRoute::PARAMETER_MODIFIERS[1]; - - if (stripos($url, $param1) !== false || stripos($url, $param) !== false) { - $url = str_ireplace([$param1, $param2], $value, $url); - } else { - $unknownParams[$param] = $value; - } - } - - $url = rtrim($url, '/') . '/' . join('/', $unknownParams); - } - - return rtrim($url, '/') . '/' . $this->arrayToParams($getParams); - } - /** * Find route by alias, class, callback or method. * * @param string $name - * @return LoadableRoute|null + * @return ILoadableRoute|null */ public function findRoute($name) { - /* @var $route LoadableRoute */ + /* @var $route ILoadableRoute */ foreach ($this->processedRoutes as $route) { /* Check if the name matches with a name on the route. Should match either router alias or controller alias. */ @@ -452,14 +406,14 @@ class RouterBase /* If nothing is defined and a route is loaded we use that */ if ($name === null && $this->loadedRoute !== null) { - return $this->processUrl($this->loadedRoute, $this->loadedRoute->getMethod(), $parameters, $getParams); + return $this->loadedRoute->findUrl($this->loadedRoute->getMethod(), $parameters, $name) . $this->arrayToParams($getParams); } /* We try to find a match on the given name */ $route = $this->findRoute($name); if ($route !== null) { - return $this->processUrl($route, $route->getMethod(), $parameters, $getParams); + return $route->findUrl($route->getMethod(), $parameters, $name) . $this->arrayToParams($getParams); } /* Using @ is most definitely a controller@method or alias@method */ @@ -468,17 +422,17 @@ class RouterBase /* Loop through all the routes to see if we can find a match */ - /* @var $route LoadableRoute */ + /* @var $route ILoadableRoute */ foreach ($this->processedRoutes as $route) { /* Check if the route contains the name/alias */ if ($route->hasName($controller)) { - return $this->processUrl($route, $method, $parameters, $getParams); + return $route->findUrl($method, $parameters, $name) . $this->arrayToParams($getParams); } /* Check if the route controller is equal to the name */ if ($route instanceof IControllerRoute && strtolower($route->getController()) === strtolower($controller)) { - return $this->processUrl($route, $method, $parameters, $getParams); + return $route->findUrl($method, $parameters, $name) . $this->arrayToParams($getParams); } } @@ -509,9 +463,9 @@ class RouterBase /** * Add bootmanager - * @param RouterBootManager $bootManager + * @param IRouterBootManager $bootManager */ - public function addBootManager(RouterBootManager $bootManager) + public function addBootManager(IRouterBootManager $bootManager) { $this->bootManagers[] = $bootManager; } @@ -558,7 +512,7 @@ class RouterBase /** * Get loaded route - * @return RouterRoute|null + * @return ILoadableRoute|null */ public function getLoadedRoute() { diff --git a/src/Pecee/SimpleRouter/RouterBootManager.php b/src/Pecee/SimpleRouter/RouterBootManager.php deleted file mode 100644 index 5419f70..0000000 --- a/src/Pecee/SimpleRouter/RouterBootManager.php +++ /dev/null @@ -1,9 +0,0 @@ -addBootManager($bootManager); } @@ -74,7 +81,7 @@ class SimpleRouter * @param string $url * @param string|\Closure $callback * @param array|null $settings - * @return RouterRoute + * @return RouteUrl */ public static function get($url, $callback, array $settings = null) { @@ -87,7 +94,7 @@ class SimpleRouter * @param string $url * @param string|\Closure $callback * @param array|null $settings - * @return RouterRoute + * @return RouteUrl */ public static function post($url, $callback, array $settings = null) { @@ -100,7 +107,7 @@ class SimpleRouter * @param string $url * @param string|\Closure $callback * @param array|null $settings - * @return RouterRoute + * @return RouteUrl */ public static function put($url, $callback, array $settings = null) { @@ -113,7 +120,7 @@ class SimpleRouter * @param string $url * @param string|\Closure $callback * @param array|null $settings - * @return RouterRoute + * @return RouteUrl */ public static function patch($url, $callback, array $settings = null) { @@ -126,7 +133,7 @@ class SimpleRouter * @param string $url * @param string|\Closure $callback * @param array|null $settings - * @return RouterRoute + * @return RouteUrl */ public static function options($url, $callback, array $settings = null) { @@ -139,7 +146,7 @@ class SimpleRouter * @param string $url * @param string|\Closure $callback * @param array|null $settings - * @return RouterRoute + * @return RouteUrl */ public static function delete($url, $callback, array $settings = null) { @@ -152,11 +159,11 @@ class SimpleRouter * @param array $settings * @param \Closure $callback * @throws \InvalidArgumentException - * @return RouterGroup + * @return RouteGroup */ public static function group(array $settings = [], \Closure $callback) { - $group = new RouterGroup(); + $group = new RouteGroup(); $group->setCallback($callback); $group->setSettings($settings); @@ -176,7 +183,7 @@ class SimpleRouter * @param callable $callback * @param array|null $settings * @see SimpleRouter::form - * @return RouterRoute + * @return RouteUrl */ public static function basic($url, $callback, array $settings = null) { @@ -191,7 +198,7 @@ class SimpleRouter * @param string|\Closure $callback * @param array|null $settings * @see SimpleRouter::form - * @return RouterRoute + * @return RouteUrl */ public static function form($url, $callback, array $settings = null) { @@ -205,11 +212,11 @@ class SimpleRouter * @param string $url * @param string|\Closure $callback * @param array|null $settings - * @return RouterRoute + * @return RouteUrl */ public static function match(array $requestMethods, $url, $callback, array $settings = null) { - $route = new RouterRoute($url, $callback); + $route = new RouteUrl($url, $callback); $route->setRequestMethods($requestMethods); $route = static::addDefaultNamespace($route); @@ -228,11 +235,11 @@ class SimpleRouter * @param string $url * @param string|\Closure $callback * @param array|null $settings - * @return RouterRoute + * @return RouteUrl */ public static function all($url, $callback, array $settings = null) { - $route = new RouterRoute($url, $callback); + $route = new RouteUrl($url, $callback); $route = static::addDefaultNamespace($route); if ($settings !== null) { @@ -250,11 +257,11 @@ class SimpleRouter * @param string $url * @param string $controller * @param array|null $settings - * @return RouterController + * @return RouteController */ public static function controller($url, $controller, array $settings = null) { - $route = new RouterController($url, $controller); + $route = new RouteController($url, $controller); $route = static::addDefaultNamespace($route); if ($settings !== null) { @@ -272,11 +279,11 @@ class SimpleRouter * @param string $url * @param string $controller * @param array|null $settings - * @return RouterResource + * @return RouteResource */ public static function resource($url, $controller, array $settings = null) { - $route = new RouterResource($url, $controller); + $route = new RouteResource($url, $controller); if ($settings !== null) { $route->setSettings($settings); @@ -351,7 +358,7 @@ class SimpleRouter */ public static function response() { - if(static::$response === null) { + if (static::$response === null) { static::$response = new Response(static::request()); } @@ -361,11 +368,11 @@ class SimpleRouter /** * Returns the router instance * - * @return RouterBase + * @return Router */ public static function router() { - return RouterBase::getInstance(); + return Router::getInstance(); } /** diff --git a/test/Dummy/DummyMiddleware.php b/test/Dummy/DummyMiddleware.php index 89820de..ebd3610 100644 --- a/test/Dummy/DummyMiddleware.php +++ b/test/Dummy/DummyMiddleware.php @@ -6,7 +6,7 @@ use Pecee\Http\Request; class DummyMiddleware implements IMiddleware { - public function handle(Request $request, \Pecee\SimpleRouter\RouterEntry &$route = null) + public function handle(Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route) { throw new MiddlewareLoadedException('Middleware loaded!'); } diff --git a/test/Dummy/Handler/ExceptionHandler.php b/test/Dummy/Handler/ExceptionHandler.php index 6b61886..a34b2db 100644 --- a/test/Dummy/Handler/ExceptionHandler.php +++ b/test/Dummy/Handler/ExceptionHandler.php @@ -2,7 +2,7 @@ class ExceptionHandler implements \Pecee\Handlers\IExceptionHandler { - public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\RouterEntry &$route = null, \Exception $error) + public function handleError(\Pecee\Http\Request $request, \Pecee\SimpleRouter\Route\ILoadableRoute &$route = null, \Exception $error) { throw $error; } diff --git a/test/GroupTest.php b/test/GroupTest.php index 1b913ea..581b2b2 100644 --- a/test/GroupTest.php +++ b/test/GroupTest.php @@ -85,17 +85,11 @@ class GroupTest extends PHPUnit_Framework_TestCase // Test method name SimpleRouter::get('/my/fancy/url/2', 'DummyController@start')->setName('fancy2'); - // Test multiple names - SimpleRouter::get('/my/fancy/url/3', 'DummyController@start', ['as' => ['fancy3', 'fancy4']]); - SimpleRouter::start(); $this->assertEquals('/my/fancy/url/1/', SimpleRouter::getRoute('fancy1')); $this->assertEquals('/my/fancy/url/2/', SimpleRouter::getRoute('fancy2')); - $this->assertEquals('/my/fancy/url/3/', SimpleRouter::getRoute('fancy3')); - $this->assertEquals('/my/fancy/url/3/', SimpleRouter::getRoute('fancy4')); - } } \ No newline at end of file diff --git a/test/RouterUrlTest.php b/test/RouterUrlTest.php index 7dc317e..69766a9 100644 --- a/test/RouterUrlTest.php +++ b/test/RouterUrlTest.php @@ -23,60 +23,78 @@ class RouterUrlTest extends PHPUnit_Framework_TestCase // Match normal route on alias SimpleRouter::get('/', 'DummyController@silent', ['as' => 'home']); - SimpleRouter::group(['prefix' => '/admin'], function() { + SimpleRouter::get('/about', 'DummyController@about'); + + SimpleRouter::group(['prefix' => '/admin', 'as' => 'admin'], function() { // Match route with prefix on alias - SimpleRouter::get('/{id?}', 'DummyController@start', ['as' => 'admin.home']); + SimpleRouter::get('/{id?}', 'DummyController@start', ['as' => 'home']); // Match controller with prefix and alias - SimpleRouter::controller('/users', 'DummyController', ['as' => 'admin.users']); + SimpleRouter::controller('/users', 'DummyController', ['as' => 'users']); // Match controller with prefix and NO alias SimpleRouter::controller('/pages', 'DummyController'); }); + SimpleRouter::group(['prefix' => 'api', 'as' => 'api'], function() { + + // Match resource controller + SimpleRouter::resource('phones', 'DummyController'); + + }); + + SimpleRouter::controller('gadgets', 'DummyController', ['names' => ['getIphoneInfo' => 'iphone']]); + // Match controller with no prefix and no alias SimpleRouter::controller('/cats', 'CatsController'); // Pretend to load page SimpleRouter::start(); + $this->assertEquals('/gadgets/iphoneinfo/', $this->getUrl('gadgets.iphone')); + + $this->assertEquals('/api/phones/create/', $this->getUrl('api.phones.create')); + // Should match / - $this->assertEquals($this->getUrl('home'), '/'); + $this->assertEquals('/', $this->getUrl('home')); + + // Should match /about/ + $this->assertEquals('/about/', $this->getUrl('DummyController@about')); // Should match /admin/ - $this->assertEquals($this->getUrl('DummyController@start'), '/admin/'); + $this->assertEquals('/admin/', $this->getUrl('DummyController@start')); // Should match /admin/ - $this->assertEquals($this->getUrl('admin.home'), '/admin/'); + $this->assertEquals('/admin/', $this->getUrl('admin.home')); // Should match /admin/2/ - $this->assertEquals($this->getUrl('admin.home', ['id' => 2]), '/admin/2/'); + $this->assertEquals('/admin/2/', $this->getUrl('admin.home', ['id' => 2])); // Should match /admin/users/ - $this->assertEquals($this->getUrl('admin.users'), '/admin/users/'); + $this->assertEquals('/admin/users/', $this->getUrl('admin.users')); // Should match /admin/users/home/ - $this->assertEquals($this->getUrl('admin.users@home'), '/admin/users/home/'); + $this->assertEquals('/admin/users/home/', $this->getUrl('admin.users@home')); // Should match /cats/ - $this->assertEquals($this->getUrl('CatsController'), '/cats/'); + $this->assertEquals('/cats/', $this->getUrl('CatsController')); // Should match /cats/view/ - $this->assertEquals($this->getUrl('CatsController', 'view'), '/cats/view/'); + $this->assertEquals('/cats/view/', $this->getUrl('CatsController', 'view')); // Should match /cats/view/ - $this->assertEquals($this->getUrl('CatsController', ['view']), '/cats/view/'); + //$this->assertEquals('/cats/view/', $this->getUrl('CatsController', ['view'])); // Should match /cats/view/666 - $this->assertEquals($this->getUrl('CatsController@view', ['666']), '/cats/view/666/'); + $this->assertEquals('/cats/view/666/', $this->getUrl('CatsController@getView', ['666'])); // Should match /funny/man/ - $this->assertEquals($this->getUrl('/funny/man'), '/funny/man/'); + $this->assertEquals('/funny/man/', $this->getUrl('/funny/man')); // Should match /?jackdaniels=true&cola=yeah - $this->assertEquals($this->getUrl('home', null, ['jackdaniels' => 'true', 'cola' => 'yeah']), '/?jackdaniels=true&cola=yeah'); + $this->assertEquals('/?jackdaniels=true&cola=yeah', $this->getUrl('home', null, ['jackdaniels' => 'true', 'cola' => 'yeah'])); }