Laravel Routing
Overview of Routing Construction
The construction methods are:
Route::get,Route::post,Route::put,Route::patch,Route::delete,Route::options,Route::any,Route::match,Route::resource,Route::resources,Route::group
Route::get('foo', function () { //Basic Mode }); Route::match(['get', 'post'], '/', function () { //Basic Mode }); Route::any('foo', function () { //Basic Mode }); Route::get('posts/{post}/comments/{comment}', function ($postId, $commentId) { //Required Routing Parameters }); Route::get('user/{name?}', function ($name = 'John') { //Optional Routing Parameters }); Route::get('user/{id}/{name}', function ($id, $name) { //Regular expression constraints })->where(['id' => '[0-9]+', 'name' => '[a-z]+']); //Global Constraint RouteServiceProvider boot method public function boot() { Route::pattern('id', '[0-9]+'); parent::boot(); } Route::get('user/{id}', function ($id) { //Execute only when {id} is a number... }); Route::get('user/profile', function () { //Named Route })->name('profile'); Route::get('user/profile', 'UserController@showProfile')->name('profile'); Generate for named routes: //Generate URL... $url = route('profile'); //Generate redirection... return redirect()->route('profile'); //Routing Group Route::group(['middleware' => 'auth'], function () { Route::get('/', function () { //Use `Auth`middleware }); Route::get('user/profile', function () { //Use `Auth`middleware }); }); Namespace|Subdomain Routing|Routing Prefix Route::group(['namespace' => 'Admin','domain' => '{account}.myapp.com','prefix' => 'admin'], function () { //In the App\Http\Controllers\Admin namespace, the subdomain name is {account}.myapp.com, and the routing prefix matches the controller of'/admin' }); Route::resource('photo', 'PhotoController', ['except' => ['create', 'store', 'update', 'destroy'], 'names' => ['create' => 'photo.build'],'middleware' => []); Routing Model Binding Implicit Binding# Laravel automatically parses the Eloquent model defined in a route or controller method that contains declared type variables that match route fragments Route::get('api/users/{user}', function (App\User $user) { return $user->email; }); Explicit Binding boot method in RouteServiceProvider class public function boot() { parent::boot(); Route::model('user', App\User::class); } Route::get('profile/{user}', function (App\User $user) { // }); Custom parsing logic public function boot() { parent::boot(); Route::bind('user', function ($value) { return App\User::where('name', $value)->first(); }); }
There are basically the following forms: uri with or without parameters, action with anonymous functions or Controller@Method, and possibly some other preconditions
Basic Constructions
Route::get,Route::post,Route::put,Route::patch,Route::delete,Route::options,Route::any,Route::match
The above construction methods are essentially the same, except for the first parameter.
public function get($uri, $action = null) { return $this->addRoute(['GET', 'HEAD'], $uri, $action); } protected function addRoute($methods, $uri, $action) { // Create a $route(\Illuminate\Routing\Route) instance and join it to the collection (\Illuminate\Routing\RouteCollection routing collection auxiliary class), then return $route return $this->routes->add($this->createRoute($methods, $uri, $action)); } protected function createRoute($methods, $uri, $action) { // $action in the form of Controller@Method|['uses'=>Controller@Method] if ($this->actionReferencesController($action)) { $action = $this->convertToControllerAction($action); } $route = $this->newRoute( $methods, $this->prefix($uri), $action ); // Set $route if prefix condition stack is not empty if ($this->hasGroupStack()) { $this->mergeGroupAttributesIntoRoute($route); } // Inject the where precondition into the $route instance $this->addWhereClausesToRoute($route); return $route; } protected function actionReferencesController($action) { if (! $action instanceof Closure) { return is_string($action) || (isset($action['uses']) && is_string($action['uses'])); } return false; } protected function convertToControllerAction($action) { if (is_string($action)) { $action = ['uses' => $action]; } // Attempt to join precondition namespace if (! empty($this->groupStack)) { $action['uses'] = $this->prependGroupNamespace($action['uses']); } // Get action from controller $action['controller'] = $action['uses']; // Similar: ['controller'=>'namespace\Controller@Method','uses'=>'namespace\Controller@Method'] return $action; } // $uri tries to increase the prefix precondition (prefix in the group corresponds to the so-called route increase below) protected function prefix($uri) { return trim(trim($this->getLastGroupPrefix(), '/').'/'.trim($uri, '/'), '/') ?: '/'; } public function getLastGroupPrefix() { if (! empty($this->groupStack)) { $last = end($this->groupStack); return isset($last['prefix']) ? $last['prefix'] : ''; } return ''; } protected function newRoute($methods, $uri, $action) { return (new Route($methods, $uri, $action)) ->setRouter($this) ->setContainer($this->container); } // new Route public function __construct($methods, $uri, $action) { $this->uri = $uri; $this->methods = (array) $methods; $this->action = $this->parseAction($action); if (in_array('GET', $this->methods) && ! in_array('HEAD', $this->methods)) { $this->methods[] = 'HEAD'; } // Try prefix uri separately if (isset($this->action['prefix'])) { $this->prefix($this->action['prefix']); } } protected function parseAction($action) { // Delegate the RouteAction action auxiliary class for resolution return RouteAction::parse($this->uri, $action); } public static function parse($uri, $action) { if (is_null($action)) { return static::missingAction($uri); // Throw an exception } // Anonymous function if (is_callable($action)) { return ['uses' => $action]; } elseif (! isset($action['uses'])) { $action['uses'] = static::findCallable($action); } // If $action['uses'] is similar to the Controller form, try to construct it as Controller@u invoke, which calls the u invoke method when no method is specified if (is_string($action['uses']) && ! Str::contains($action['uses'], '@')) { $action['uses'] = static::makeInvokable($action['uses']); } return $action; } protected static function findCallable(array $action) { // Try to find the first value from the $action array that satisfies the callable number key and returns as $action return Arr::first($action, function ($value, $key) { return is_callable($value) && is_numeric($key); }); } public function hasGroupStack() { return ! empty($this->groupStack); } protected function mergeGroupAttributesIntoRoute($route) { $route->setAction($this->mergeWithLastGroup($route->getAction())); } public function mergeWithLastGroup($new) { // Use previous groupStack settings return RouteGroup::merge($new, end($this->groupStack)); } protected function addWhereClausesToRoute($route) { $route->where(array_merge( $this->patterns, isset($route->getAction()['where']) ? $route->getAction()['where'] : [] )); return $route; } // Return the \Illuminate\Routing\Route instance public function add(Route $route) { $this->addToCollections($route); $this->addLookups($route); return $route; } protected function addToCollections($route) { $domainAndUri = $route->domain().$route->uri(); foreach ($route->methods() as $method) { $this->routes[$method][$domainAndUri] = $route; } $this->allRoutes[$method.$domainAndUri] = $route; } protected function addLookups($route) { $action = $route->getAction(); // If the precondition stack is set as, $route is injected into the $this->nameList if (isset($action['as'])) { $this->nameList[$action['as']] = $route; } if (isset($action['controller'])) { $this->addToActionList($action, $route); } } protected function addToActionList($action, $route) { $this->actionList[trim($action['controller'], '\\')] = $route; }
Process Summary (Routes are created and added to the routing collection for unified management)
Depending on the form and preconditions of the action, or into an array (['use'=> Clause | namespace Controller@Method]), or as an anonymous function
Depending on the precondition, or prefix the group uri
Create route instances, unify action s into arrays, and do some other settings
If preconditions exist, add to the action array of the route instance
route instance adds where condition
Other Constructions
Route::group
public function group(array $attributes, $routes) { $this->updateGroupStack($attributes); $this->loadRoutes($routes); array_pop($this->groupStack); } protected function updateGroupStack(array $attributes) { if (! empty($this->groupStack)) { $attributes = RouteGroup::merge($attributes, end($this->groupStack)); } $this->groupStack[] = $attributes; } protected function loadRoutes($routes) { if ($routes instanceof Closure) { $routes($this); // Note: Each anonymous function will have a router instance } else { $router = $this; require $routes; } }
Summary
Essentially or essentially by setting a precondition stack ($groupStack) and then applying it to all members of the group
Route::resource,Route::resources
public function resource($name, $controller, array $options = []) { if ($this->container && $this->container->bound(ResourceRegistrar::class)) { $registrar = $this->container->make(ResourceRegistrar::class); } else { $registrar = new ResourceRegistrar($this); } $registrar->register($name, $controller, $options); } public function __construct(Router $router) { $this->router = $router; } public function register($name, $controller, array $options = []) { if (isset($options['parameters']) && ! isset($this->parameters)) { $this->parameters = $options['parameters']; } if (Str::contains($name, '/')) { $this->prefixedResource($name, $controller, $options); return; } $base = $this->getResourceWildcard(last(explode('.', $name))); // ['index', 'create', 'store', 'show', 'edit', 'update', 'destroy'] $defaults = $this->resourceDefaults; // Generate Routes Under Corresponding Conditions foreach ($this->getResourceMethods($defaults, $options) as $m) { $this->{'addResource'.ucfirst($m)}($name, $base, $controller, $options); } } protected function prefixedResource($name, $controller, array $options) { list($name, $prefix) = $this->getResourcePrefix($name); // $me is a router instance.The essence is to convert a resource request with $name of'xx/yy/zz'into a groupStack append ['prefix'=>'xx/yy'] request within the group, and the corresponding anonymous function is still a resource request with $name of'zz' $callback = function ($me) use ($name, $controller, $options) { $me->resource($name, $controller, $options); }; return $this->router->group(compact('prefix'), $callback); } protected function getResourcePrefix($name) { $segments = explode('/', $name); $prefix = implode('/', array_slice($segments, 0, -1)); // Return ['zz','xx/yy'] if $name is'xx/yy/zz'] return [end($segments), $prefix]; } // Prefer values from settings, or generate singular strings and replace the character'-'with'' public function getResourceWildcard($value) { if (isset($this->parameters[$value])) { $value = $this->parameters[$value]; } elseif (isset(static::$parameterMap[$value])) { $value = static::$parameterMap[$value]; } elseif ($this->parameters === 'singular' || static::$singularParameters) { $value = Str::singular($value); } return str_replace('-', '_', $value); } protected function getResourceMethods($defaults, $options) { if (isset($options['only'])) { return array_intersect($defaults, (array) $options['only']); } elseif (isset($options['except'])) { return array_diff($defaults, (array) $options['except']); } return $defaults; } protected function addResourceIndex($name, $base, $controller, $options) { $uri = $this->getResourceUri($name); $action = $this->getResourceAction($name, $controller, 'index', $options); return $this->router->get($uri, $action); } public function getResourceUri($resource) { if (! Str::contains($resource, '.')) { return $resource; } $segments = explode('.', $resource); $uri = $this->getNestedResourceUri($segments); // 'xx/{xx}/yy/{yy}/zz' return str_replace('/{'.$this->getResourceWildcard(end($segments)).'}', '', $uri); } protected function getNestedResourceUri(array $segments) { // ['xx','yy','zz'] => 'xx/{xx}/yy/{yy}/zz/{zz}' return implode('/', array_map(function ($s) { return $s.'/{'.$this->getResourceWildcard($s).'}'; }, $segments)); } protected function getResourceAction($resource, $controller, $method, $options) { $name = $this->getResourceRouteName($resource, $method, $options); $action = ['as' => $name, 'uses' => $controller.'@'.$method]; if (isset($options['middleware'])) { $action['middleware'] = $options['middleware']; } return $action; } protected function getResourceRouteName($resource, $method, $options) { $name = $resource; if (isset($options['names'])) { if (is_string($options['names'])) { $name = $options['names']; } elseif (isset($options['names'][$method])) { return $options['names'][$method]; } } $prefix = isset($options['as']) ? $options['as'].'.' : ''; return trim(sprintf('%s%s.%s', $prefix, $name, $method), '.'); }
Summary
Constructions of resource types are actually translated into routes that construct multiple default resources, which are essentially still basic constructs