Skip to content
Merged
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
17 changes: 17 additions & 0 deletions src/Illuminate/Routing/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,23 @@ abstract class Controller {
*/
protected $layout;

/**
* Register middleware on the controller (L13 idiom).
*
* BC bridge: middleware registered here runs via the legacy controller-filter
* path (ControllerDispatcher) until the routing engine is swapped to stock L13.
* The signature matches L13's Controller::middleware($middleware, array $options),
* so the '['only'=>..]'/'['except'=>..]' options carry over unchanged after the swap.
*
* @param \Closure|string $middleware
* @param array $options
* @return void
*/
public function middleware($middleware, array $options = array())
{
$this->beforeFilter($middleware, $options);
}

/**
* Register a "before" filter on the controller.
*
Expand Down
93 changes: 93 additions & 0 deletions src/Illuminate/Routing/MiddlewareNameResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

namespace Illuminate\Routing;

use Closure;
use LogicException;
use Throwable;

class MiddlewareNameResolver
{
/**
* Resolve the middleware name to a class name(s) preserving passed parameters.
*
* @param \Closure|string $name
* @param array $map
* @param array $middlewareGroups
* @return \Closure|string|array
*/
public static function resolve($name, $map, $middlewareGroups)
{
// When the middleware is simply a Closure, we will return this Closure instance
// directly so that Closures can be registered as middleware inline, which is
// convenient on occasions when the developers are experimenting with them.
if ($name instanceof Closure) {
return $name;
}

if (isset($map[$name]) && $map[$name] instanceof Closure) {
return $map[$name];
}

// If the middleware is the name of a middleware group, we will return the array
// of middlewares that belong to the group. This allows developers to group a
// set of middleware under single keys that can be conveniently referenced.
if (isset($middlewareGroups[$name])) {
return static::parseMiddlewareGroup($name, $map, $middlewareGroups);
}

// Finally, when the middleware is simply a string mapped to a class name the
// middleware name will get parsed into the full class name and parameters
// which may be run using the Pipeline which accepts this string format.
[$name, $parameters] = array_pad(explode(':', $name, 2), 2, null);

return ($map[$name] ?? $name).(! is_null($parameters) ? ':'.$parameters : '');
}

/**
* Parse the middleware group and format it for usage.
*
* @param string $name
* @param array $map
* @param array $middlewareGroups
* @return array
*
* @throws \Throwable
*/
protected static function parseMiddlewareGroup($name, $map, $middlewareGroups)
{
$results = [];

foreach ($middlewareGroups[$name] as $middleware) {
// If the middleware is another middleware group we will pull in the group and
// merge its middleware into the results. This allows groups to conveniently
// reference other groups without needing to repeat all their middlewares.
if (isset($middlewareGroups[$middleware])) {
if ($name === $middleware) {
throw new LogicException("[$name] middleware group is referencing itself.");
}

$results = array_merge($results, static::parseMiddlewareGroup(
$middleware, $map, $middlewareGroups
));

continue;
}

[$middleware, $parameters] = array_pad(
explode(':', $middleware, 2), 2, null
);

// If this middleware is actually a route middleware, we will extract the full
// class name out of the middleware list now. Then we'll add the parameters
// back onto this class' name so the pipeline will properly extract them.
if (isset($map[$middleware])) {
$middleware = $map[$middleware];
}

$results[] = $middleware.($parameters ? ':'.$parameters : '');
}

return $results;
}
}
40 changes: 40 additions & 0 deletions src/Illuminate/Routing/Route.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,46 @@ public function run()
return call_user_func_array($this->action['uses'], array_values($parameters));
}

/**
* Get or set the middlewares attached to the route.
*
* @param array|string|null $middleware
* @return $this|array
*/
public function middleware($middleware = null)
{
if (is_null($middleware))
{
return (array) ($this->action['middleware'] ?? []);
}

if ( ! is_array($middleware))
{
$middleware = func_get_args();
}

foreach ($middleware as $index => $value)
{
$middleware[$index] = $value instanceof \Closure ? $value : (string) $value;
}

$this->action['middleware'] = array_merge(
(array) ($this->action['middleware'] ?? []), $middleware
);

return $this;
}

/**
* Get all middleware, including the ones from the controller.
*
* @return array
*/
public function gatherMiddleware()
{
return Router::uniqueMiddleware($this->middleware());
}

/**
* Determine if the route matches given request.
*
Expand Down
162 changes: 161 additions & 1 deletion src/Illuminate/Routing/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use Illuminate\Http\Response;
use Illuminate\Events\Dispatcher;
use Illuminate\Container\Container;
use Illuminate\Pipeline\Pipeline;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
Expand Down Expand Up @@ -75,6 +77,27 @@ class Router implements HttpKernelInterface, RouteFiltererInterface {
*/
protected array $regexFilters = array();

/**
* All of the short-hand keys for middlewares (L13 route-middleware engine).
*
* @var array
*/
protected array $middleware = array();

/**
* All of the middleware groups.
*
* @var array
*/
protected array $middlewareGroups = array();

/**
* The priority-sorted list of middleware.
*
* @var array
*/
protected array $middlewarePriority = array();

/**
* The registered route value binders.
*
Expand Down Expand Up @@ -1066,7 +1089,7 @@ public function dispatchToRoute(Request $request)

if (is_null($response))
{
$response = $route->run($request);
$response = $this->runRouteWithinStack($route, $request);
}

$response = $this->prepareResponse($request, $response);
Expand All @@ -1079,6 +1102,143 @@ public function dispatchToRoute(Request $request)
return $response;
}

/**
* Run the matched route through its middleware stack, then execute it.
*
* L13 route-middleware runs here, INSIDE the legacy before/after filter
* bracket. Routes without middleware short-circuit to a plain run() so
* existing (filter-only) routes are byte-for-byte unaffected.
*
* @param Route $route
* @param Request $request
* @return mixed
*/
protected function runRouteWithinStack(Route $route, Request $request)
{
$middleware = $this->gatherRouteMiddleware($route);

if (empty($middleware))
{
return $route->run();
}

return (new Pipeline($this->container))
->send($request)
->through($middleware)
->then(function() use ($route)
{
return $route->run();
});
}

/**
* Gather the middleware for the given route with resolved class names.
*
* @param Route $route
* @return array
*/
public function gatherRouteMiddleware(Route $route)
{
return $this->resolveMiddleware($route->gatherMiddleware());
}

/**
* Resolve a flat array of middleware classes from names/aliases/groups.
*
* @param array $middleware
* @return array
*/
public function resolveMiddleware(array $middleware)
{
$resolved = (new Collection($middleware))
->map(function($name)
{
return (array) MiddlewareNameResolver::resolve($name, $this->middleware, $this->middlewareGroups);
})
->flatten()
->values()
->all();

return $this->sortMiddleware($resolved);
}

/**
* Sort the given middleware by priority.
*
* @param array $middleware
* @return array
*/
protected function sortMiddleware(array $middleware)
{
return (new SortedMiddleware($this->middlewarePriority, $middleware))->all();
}

/**
* Register a short-hand name for a middleware.
*
* @param string $name
* @param string $class
* @return $this
*/
public function aliasMiddleware($name, $class)
{
$this->middleware[$name] = $class;

return $this;
}

/**
* Register a group of middleware.
*
* @param string $name
* @param array $middleware
* @return $this
*/
public function middlewareGroup($name, array $middleware)
{
$this->middlewareGroups[$name] = $middleware;

return $this;
}

/**
* Set the priority-sorted list of middleware.
*
* @param array $middleware
* @return $this
*/
public function middlewarePriority(array $middleware)
{
$this->middlewarePriority = $middleware;

return $this;
}

/**
* Remove any duplicate middleware from the given array.
*
* @param array $middleware
* @return array
*/
public static function uniqueMiddleware(array $middleware)
{
$seen = array();
$result = array();

foreach ($middleware as $value)
{
$key = is_object($value) ? spl_object_id($value) : $value;

if ( ! isset($seen[$key]))
{
$seen[$key] = true;
$result[] = $value;
}
}

return $result;
}

/**
* Find the route matching a given request.
*
Expand Down
Loading
Loading