Skip to content

Allow for segmented parameters as proposed in #176 #252

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/Illuminate/Routing/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ class Route extends BaseRoute {
*/
protected $parsedParameters;

/**
* The registered segmented keys.
*
* @var array
*/
protected $segmented = array();

/**
* Execute the route and return the response.
*
Expand Down Expand Up @@ -193,6 +200,10 @@ public function getParameters()

$variables = $this->compile()->getVariables();

// We will now reduce the amount of variables to the amount of input parameters
// so that segmented parameters will be joined together into one array.
$variables = $this->reduceSegmentedVariables($variables);

// To get the parameter array, we need to spin the names of the variables on
// the compiled route and match them to the parameters that we got when a
// route is matched by the router, as routes instances don't have them.
Expand All @@ -206,6 +217,42 @@ public function getParameters()
return $this->parsedParameters = $parameters;
}

/**
* Reduce all segmented variables back to one input parameter.
*
* @param array $variables
* @return array
*/
protected function reduceSegmentedVariables($variables)
{
foreach ($this->segmented as $variable => $segments)
{
// To replace the segmented variables with their original key, we need
// the position of the first variable so we can replace all variables
// from that position with just the one variable.
$offset = array_search(head($segments), $variables);

array_splice($variables, $offset, count($segments), $variable);

// It is also necessary to actually provide the segmented parameter so we
// will create it by combining all segments for the substitude parameter.
$this->combineParameters($segments, $variable);
}

return $variables;
}

/**
* Create the input parameter for reduced segments.
*
* @param array $keys
* @return void
*/
protected function combineParameters($keys, $substitute)
{
$this->parameters[$substitute] = array_only($this->parameters, $keys);
}

/**
* Resolve a parameter value for the route.
*
Expand Down Expand Up @@ -398,6 +445,17 @@ public function setParameters($parameters)
$this->parameters = $parameters;
}

/**
* Set the segmented parameters on the route.
*
* @param array $segmented
* @return void
*/
public function setSegmented($segmented)
{
$this->segmented = $segmented;
}

/**
* Set the Router instance on the route.
*
Expand Down
55 changes: 54 additions & 1 deletion src/Illuminate/Routing/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ class Router {
*/
protected $binders = array();

/**
* The registered segmented keys.
*
* @var array
*/
protected $segmented = array();

/**
* The current request being dispatched.
*
Expand Down Expand Up @@ -522,7 +529,11 @@ protected function createRoute($method, $pattern, $action)
$action = array_merge($this->groupStack[$index], $action);
}

// Next we will parse the pattern and add any specified prefix to the it so
// We will first replace all parameters that represent multiple segments by
// expanding them to get the fully qualified pattern containing all parameters.
list($pattern, $segmented) = $this->parseSegmentedVariables($pattern);

// Next we will parse the pattern and add any specified prefix to it so
// a common URI prefix may be specified for a group of routes easily and
// without having to specify them all for every route that is defined.
list($pattern, $optional) = $this->getOptional($pattern);
Expand All @@ -545,6 +556,8 @@ protected function createRoute($method, $pattern, $action)

$route->setRequirement('_method', $method);

$route->setSegmented($segmented);

// Once we have created the route, we will add them to our route collection
// which contains all the other routes and is used to match on incoming
// URL and their appropriate route destination and on URL generation.
Expand Down Expand Up @@ -638,6 +651,34 @@ protected function setAttributes(Route $route, $action, $optional)
}
}

/**
* Parse the pattern by replacing al defined segmented parameters with
* their substituted named segments.
*
* @param string $pattern
* @return array
*/
protected function parseSegmentedVariables($pattern)
{
$segmented = array();

foreach ($this->segmented as $variable => $segments)
{
$search = '{'.$variable.'}';

if (str_contains($pattern, $search))
{
$replace = '{'.implode('}/{', $segments).'}';

$pattern = str_replace($search, $replace, $pattern);

$segmented[$variable] = $segments;
}
}

return array($pattern, $segmented);
}

/**
* Modify the pattern and extract optional parameters.
*
Expand Down Expand Up @@ -1083,6 +1124,18 @@ public function performBinding($key, $value, $route)
return call_user_func($this->binders[$key], $value, $route);
}

/**
* Register a key which will be substituted as multiple segments.
*
* @param string $key
* @param array $segments
* @return void
*/
public function segments($key, array $segments)
{
$this->segmented[$key] = $segments;
}

/**
* Prepare the given value as a Response object.
*
Expand Down
64 changes: 64 additions & 0 deletions tests/Routing/RoutingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,70 @@ public function testRoutesArentOverriddenBySubDomainWithGroups()
$this->assertEquals('sub', $router->dispatch($request)->getContent());
}

public function testSegmentedParametersAreSubstituted()
{
$router = new Router(new Illuminate\Container\Container);
$router->segments('user', array('firstname', 'lastname'));
$router->get('users/{user}', function($user) { return $user['firstname'].$user['lastname']; } );
$router->get('users/{user}/{age}', function($user, $age) { return $user['firstname'].$user['lastname'].$age; } );

$request = Request::create('/users/taylor/otwell', 'GET');
$this->assertEquals('taylorotwell', $router->dispatch($request)->getContent());

$request = Request::create('/users/taylor/otwell/25', 'GET');
$this->assertEquals('taylorotwell25', $router->dispatch($request)->getContent());
}

public function testSegmentedParametersCreatePattern()
{
$router = new Router(new Illuminate\Container\Container);
$router->segments('user', array('firstname', 'lastname'));
$r1 = $router->get('users/{user}', function($user) { return $user['firstname'].$user['lastname']; } );
$r2 = $router->get('users/{user}/{age}', function($user, $age) { return $user['firstname'].$user['lastname'].$age; } );

$this->assertEquals('/users/{firstname}/{lastname}', $r1->getPattern());
$this->assertEquals('/users/{firstname}/{lastname}/{age}', $r2->getPattern());
}

public function testSegmentedParametersCanBeBound()
{
$router = new Router(new Illuminate\Container\Container);
$router->segments('user', array('firstname', 'lastname'));
$router->bind('user', function($value) { return (object)$value; });
$router->get('users/{user}', function(stdClass $user) { return $user->firstname.$user->lastname; } );

$request = Request::create('/users/taylor/otwell', 'GET');
$this->assertEquals('taylorotwell', $router->dispatch($request)->getContent());
}

/**
* @expectedException Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function testSegmentedParametersShouldBeFullySpecified()
{
$router = new Router(new Illuminate\Container\Container);
$router->segments('user', array('firstname', 'lastname'));
$router->bind('user', function($value) { return (object)$value; });
$router->get('users/{user}', function(stdClass $user) { return $user->firstname.$user->lastname; } );

$request = Request::create('/users/taylor', 'GET');
$this->assertEquals('taylor', $router->dispatch($request)->getContent());
}

public function testSegmentedParametersAreProperlySubstituted()
{
$router = new Router(new Illuminate\Container\Container);
$router->segments('user', array('firstname', 'lastname'));
$router->get('users/{user}', function($user) { return $user['firstname'].$user['lastname']; } );
$router->get('about/{firstname}/{lastname}', function($firstname, $lastname) { return $firstname.$lastname; } );

$request = Request::create('/users/taylor/otwell', 'GET');
$this->assertEquals('taylorotwell', $router->dispatch($request)->getContent());

$request = Request::create('/about/taylor/otwell', 'GET');
$this->assertEquals('taylorotwell', $router->dispatch($request)->getContent());
}

}

class RoutingModelBindingStub {
Expand Down