Skip to content

Latest commit

 

History

History
651 lines (504 loc) · 21 KB

routing.md

File metadata and controls

651 lines (504 loc) · 21 KB

Routing

⬆️ Menú principal ⬅️ Anterior (Views) ➡️ Siguiente (Validation)

Route group within a group

In Routes, you can create a group within a group, assigning a certain middleware only to some URLs in the "parent" group.

Route::group(['prefix' => 'account', 'as' => 'account.'], function() {
    Route::get('login', [AccountController::class, 'login']);
    Route::get('register', [AccountController::class, 'register']);
    Route::group(['middleware' => 'auth'], function() {
        Route::get('edit', [AccountController::class, 'edit']);
    });
});

Declare a resolveRouteBinding method in your Model

Route model binding proveé una conveniente manera de injéctar instacias de nuestros Models directamente en las rutas, esto es bastante util, pero hay casos donde no podemos permitir fácilmente el acceso mediante ID's. Quizá necesitamos verificar algunas cosas más.

public function resolveRouteBinding($value, $field = null)
{
     $user = request()->user();

     return $this->where([
          ['user_id' => $user->id],
          ['id' => $value]
     ])->firstOrFail();
}

⭐ Aportación de @notdylanv

assign withTrashed() to Route::resource() method

Antes de Laravel 9.35 para buscar usuarios eliminados lógicamente debíamos hacer esto:

Route::get('/users/{user}', function (User $user) {
     return $user->email;
})->withTrashed();

Desde Laravel 9.35 podemos asignar a un resource.

Route::resource('users', UserController::class)
     ->withTrashed();

Incluso podemos determinar los métodos.

Route::resource('users', UserController::class)
     ->withTrashed(['show']);

Skip Input Normalization

Laravel automáticamente elimina los espacios en todos los strings de las peticiones. Es llamado input normalización.

Aveces, quizá no querrás este comportamiento.

Para esto puedes usar el método skipWhen en el middleware TrimString y retornar true para saltarlo.

public function boot()
{
     TrimStrings::skipWhen(function ($request) {
          return $request->is('admin/*);
     });
}

⭐ Aportación de @Laratips1

Wildcard subdomains

Puedes crear un grupo de rutas por cada nombre de subdominio dinámico y pasar le su valor en cada ruta,

Route::domain('{username}.workspace.com')->group(function () {
    Route::get('user/{id}', function ($username, $id) {
        //
    });
});

What's behind the routes?

Si ocupas el paquete Laravel UI package, probablemente quieras saber que rutas hay detrás de Auth::routes().

public function auth()
{
    return function ($options = []) {
        // Authentication Routes...
        $this->get('login', 'Auth\LoginController@showLoginForm')->name('login');
        $this->post('login', 'Auth\LoginController@login');
        $this->post('logout', 'Auth\LoginController@logout')->name('logout');
        // Registration Routes...
        if ($options['register'] ?? true) {
            $this->get('register', 'Auth\RegisterController@showRegistrationForm')->name('register');
            $this->post('register', 'Auth\RegisterController@register');
        }
        // Password Reset Routes...
        if ($options['reset'] ?? true) {
            $this->resetPassword();
        }
        // Password Confirmation Routes...
        if ($options['confirm'] ?? class_exists($this->prependGroupNamespace('Auth\ConfirmPasswordController'))) {
            $this->confirmPassword();
        }
        // Email Verification Routes...
        if ($options['verify'] ?? false) {
            $this->emailVerification();
        }
    };
}

La linea que nos trae todo esto no tiene parámetros.

Auth::routes(); // no parameters

Pero puedes proveer parámetros para habilitar o invalidar ciertas rutas.

Auth::routes([
    'login'    => true,
    'logout'   => true,
    'register' => true,
    'reset'    => true,  // for resetting passwords
    'confirm'  => false, // for additional password confirmations
    'verify'   => false, // for email verification
]);

⭐ Tip basado en la sugerencia de MimisK13

Route Model Binding: You can define a key

Puedes Route model binding de la siguiente manera: Route::get('api/users/{user}', function (User $user) { … }, pero no solo por el campo de ID. Si deseas que {user} sea el campo username, coloca lo siguiente en el modelo:

public function getRouteKeyName() {
    return 'username';
}

Route Fallback: When no Other Route is Matched

Si quieres especificar una lógica adicional para rutas no encontradas, en vez de solo lanzar la página 403, puedes crear una ruta especial para eso, al final de las rutas agrega esto:

Route::group(['middleware' => ['auth'], 'prefix' => 'admin', 'as' => 'admin.'], function () {
    Route::get('/home', [HomeController::class, 'index']);
    Route::resource('tasks', [Admin\TasksController::class]);
});

// Some more routes....
Route::fallback(function() {
    return 'Hm, why did you land here somehow?';
});

Route Parameters Validation with RegExp

Podemos validar parámetros directamente en la ruta con el parámetro "where". Un caso bastante típico es colocar un prefijo en las rutas por idioma local como: es/blog and en/article/333. Para asegurarnos de esto hacemos:

routes/web.php:

Route::group([
    'prefix' => '{locale}',
    'where' => ['locale' => '[a-zA-Z]{2}']
], function () {
    Route::get('/', [HomeController::class, 'index']);
    Route::get('article/{id}', [ArticleController::class, 'show']);;
});

Rate Limiting: Global and for Guests/Users

Podemos limitar el máximo de llamadas de una URL de 60 veces por minuto con throttle:60,1:

Route::middleware('auth:api', 'throttle:60,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

Pero también podemos hacerlo separadamente por público y para personas logeadas.

// maximum of 10 requests for guests, 60 for authenticated users
Route::middleware('throttle:10|60,1')->group(function () {
    //
});

Además puedes tener un campo rate_limit en la tabla usuarios para tomar la cantidad:

Route::middleware('auth:api', 'throttle:rate_limit,1')->group(function () {
    Route::get('/user', function () {
        //
    });
});

Query string parameters to Routes

Si pasas adicionales parámetros a una ruta, esas llaves y valores serán automáticamente añadidas a la ruta generada.

Route::get('user/{id}/profile', function ($id) {
    //
})->name('profile');

$url = route('profile', ['id' => 1, 'photos' => 'yes']); // Result: /user/1/profile?photos=yes

Separate Routes by Files

Si tienes rutas relacionadas a ciertos módulos, puedes separarlas en un archivo routes/XXXXX.php e incluirla en el archivo routes/web.php.

Un ejemplo con las rutas de Laravel Breeze :

Route::get('/', function () {
    return view('welcome');
});

Route::get('/dashboard', function () {
    return view('dashboard');
})->middleware(['auth'])->name('dashboard');

require __DIR__.'/auth.php';

Tu archivo routes/auth.php: quedaría así:

use App\Http\Controllers\Auth\AuthenticatedSessionController;
use App\Http\Controllers\Auth\RegisteredUserController;
// ... more controllers

use Illuminate\Support\Facades\Route;

Route::get('/register', [RegisteredUserController::class, 'create'])
                ->middleware('guest')
                ->name('register');

Route::post('/register', [RegisteredUserController::class, 'store'])
                ->middleware('guest');

// ... A dozen more routes

Pero debería usar include() solo cuando ese archivo separado tiene las mismas configuración para el prefijo/middlewares de otra manera es mejor agruparlas en app/Providers/RouteServiceProvider:

public function boot()
{
    $this->configureRateLimiting();

    $this->routes(function () {
        Route::prefix('api')
            ->middleware('api')
            ->namespace($this->namespace)
            ->group(base_path('routes/api.php'));

        Route::middleware('web')
            ->namespace($this->namespace)
            ->group(base_path('routes/web.php'));

        // ... Your routes file listed next here
    });
}

Translate Resource Verbs

Si usas controladores tipo resource, pero quieres cambiar los verbos que aparecen en la URL a otro idioma para propósitos de SEO, en vez de que aparezca /create aparecerá así: /crear, para configurar lo coloca este código en el método boot de App\Providers\RouteServiceProvider:

public function boot()
{
    Route::resourceVerbs([
        'create' => 'crear',
        'edit' => 'editar',
    ]);

    // ...
}

Custom Resource Route Names

Cuando usamos controladores tipo resources in routes/web.php puedes especificar los nombres de las rutas, así el prefijo de la URL en el navegador y el prefijo de la ruta puede ser diferente.

Route::resource('p', ProductController::class)->names('products');

Este código generará URL como /p, /p/{id}, /p/{id}/edit, . Pero para llamarlos como nombre de ruta: route('products.index'), route('products.create'), etc.

Eager load relationship

Si usas la función de Route Model Binding y piensas que no puedes usar Eager Loading para relaciones, piénsalo de nuevo

public function show(Product $product) {
    //
}

Aunque tengas relaciones tipo belongsTo, puedes usar la función ->load()

public function show(Product $product) {
    $product->load('category');
    //
}

Localizing Resource URIs

Si utilizas controladores tipo resource, pero quieres cambiar los verbos de la URL a otro idioma. En vez de que la ruta aparezca así /create debería aparecer en español /crear. Para esto debes configurar los en el método Route::resourceVerbs() .

public function boot()
{
    Route::resourceVerbs([
        'create' => 'crear',
        'edit' => 'editar',
    ]);
    //
}

Resource Controllers naming

En los controladores de recursos (Resource Controllers), en routes/web.php, puedes especificar el parámetro ->names(), de modo que el prefijo de la URL y el prefijo del nombre de la ruta puedan ser diferentes.

Esto generará URLs como /p, /p/{id}, /p/{id}/edit, etc. Pero podrías llamarlos de la siguiente manera:

  • route('products.index)
  • route('products.create)
  • etc.
Route::resource('p', \App\Http\Controllers\ProductController::class)->names('products');

Easily highlight your navbar menus

Usa el Route::is('route-name') para fácilmente resaltar tus menus navbar.

<ul>
    <li @if(Route::is('home')) class="active" @endif>
        <a href="/">Home</a>
    </li>
    <li @if(Route::is('contact-us')) class="active" @endif>
        <a href="/contact-us">Contact us</a>
    </li>
</ul>

⭐ Aportación de @anwar_nairi

Generate absolute path using route() helper

route('page.show', $page->id);
// https://laravel.test/pages/1

route('page.show', $page->id, false);
// /pages/1

⭐ Aportación de @oliverds_

Override the route binding resolver for each of your models

Puedes sobrescribir el route binding para cada uno de tus modelos. En este ejemplo, no tengo control sobre el signo @ en la URL, por lo que utilizando el método resolveRouteBinding, puedo eliminar el signo @ y resolver el modelo.

// Ruta 
Route::get('{product:slug}', Controller::class);

// Petición
https://nodejs.pub/@unlock/hello-world

// Modelo Producto
public function resolveRouteBinding($value, $field = null)
{
    $value = str_replace('@', '', $value);

    return parent::resolveRouteBinding($value, $field);
}

⭐ Aportación de @Philo01

If you need public URL, but you want them to be secured

Si necesitas una URL pública pero quieres mantener la asegurada, usa Laravel signed URL.

class AccountController extends Controller
{
    public function destroy(Request $request)
    {
        $confirmDeleteUrl = URL::signedRoute('confirm-destroy', [
            $user => $request->user()
        ]);
        // Send link by email...
    }

    public function confirmDestroy(Request $request, User $user)
    {
        if (! $request->hasValidSignature()) {
            abort(403);
        }

        // User confirmed by clicking on the email
        $user->delete();

        return redirect()->route('home');
    }
}

⭐ Aportación de @anwar_nairi

Using Gate in middleware method

Puedes utilizar las puertas (gates) que especificaste en App\Providers\AuthServiceProvider en el método de middleware.

Para hacer esto, solo necesitas colocar dentro de can: los nombres de las puertas necesarias.

Route::put('/post/{post}', function (Post $post) {
    // The current user may update the post...
})->middleware('can:update,post');

Simple route with arrow function

Puedes usar una función de flecha en las rutas, sin tener que usar una función anónima.

Para hacer esto, solo tienes que usar fn() =>.

// Instead of
// En vez de esto
Route::get('/example', function () {
    return User::all();
});

// You can
// Pudes usar 
Route::get('/example', fn () => User::all());

Route view

Puedes definir Route::view($uri , $bladePage) para retornar una vista directamente, sin tener que usar un controlador.

//this will return home.blade.php view
Route::view('/home', 'home');

Route directory instead of route file

También puedes crear un directorio de rutas así:/routes/web/, para tener más organización y solo llamar en el archivo web.php lo siguiente:

foreach(glob(dirname(__FILE__).'/web/*', GLOB_NOSORT) as $route_file){
    include $route_file;
}

Ahora cada archivo dentro de /routes/web/ actuará como un archivo router web donde puedes tener diferentes rutas en muchos archivos.

Route resources grouping

Si en tus rutas tienes muchos controladores tipo resources, puedes agruparlos y llamar los en Route::resources() en vez de definir cada ruta por igual.

Route::resources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);

Custom route bindings

¿Sabías que puedes definir route bindings personalizados?

En este ejemplo, necesito buscar un portafolio por slug. Pero el slug no es único, porque multiples usuarios pueden tener un portafolio llamado 'Foo'. Así que defino como Laravel puede buscar lo desde un parámetro de la ruta.

class RouteServiceProvider extends ServiceProvider
{
    public const HOME = '/dashboard';

    public function boot()
    {
        Route::bind('portfolio', function (string $slug) {
            return Portfolio::query()
                ->whereBelongsto(request()->user())
                ->whereSlug($slug)
                ->firstOrFail();
        });
    }
}
Route::get('portfolios/{portfolio}', function (Portfolio $portfolio) {
    /*
     * The $portfolio will be the result of the query defined in the RouteServiceProvider
 * El $portafolio será el resultado de la  busqueda definida en el RouteServiceProvider.
     */
})

⭐Aportación de @mmartin_joo

Two ways to check the route name

Aquí tenemos dos maneras de revisar el nombre de la ruta en Laravel.

// #1
<a
    href="{{ route('home') }}"
    @class="['navbar-link', 'active' => Route::current()->getName() === 'home']"
>
    Home
</a>
// #2
<a
    href="{{ route('home') }}"
    @class="['navbar-link', 'active' => request()->routeIs('home)]"
>
    Home
</a>

⭐Aportación de @AndrewSavetchuk

Route model binding soft-deleted models

Por defecto, cuando usamos el route model binding no podremos traer modelos que han sido borrados con el soft-delete. Para evitar este comportamiento debemos usar el método withTrashed en la ruta.

Route::get('/posts/{post}', function (Post $post) {
    return $post;
})->withTrashed();

⭐Aportación de @cosmeescobedo

Retrieve the URL without query parameters

SI por alguna razón, tú URL tiene query parameters, puedes remover esos query parameters usando el método fullUrlWithoutQuery sobre la petición así:

// Original  URL: https://www.amitmerchant.com?search=laravel&lang=en&sort=desc
$urlWithQueryString = $request->fullUrlWithoutQuery([
    'lang',
    'sort'
]);
echo $urlWithQueryString;
// Salida:  https://www.amitmerchant.com?search=laravel

⭐Aportación de @amit_merchant

Customizing Missing Model Behavior in route model bindings

Por defecto, Laravel lanza un excepción 404 cuando no puede hacer un bind al modelo, puedes cambiar el comportamiento pasando le una función al método missing.

Route::get('/users/{user}', [UsersController::class, 'show'])
    ->missing(function ($parameters) {
        return Redirect::route('users.index');
    });

⭐Aportación de @cosmeescobedo

Exclude middleware from a route

Puedes excluir middlewares de cualquier ruta usando el método withoutMiddleware.

Route::post('/some/route', SomeController::class)
    ->withoutMiddleware([VerifyCsrfToken::class]);

⭐ Aportación de @alexjgarrett

Controller groups

En vez de usar el controlador en cada ruta, puedes considerar usar un route controller group. Este fue añadido desde la versión 8.80.

// Antes
Route::get('users', [UserController::class, 'index']);
Route::post('users', [UserController::class, 'store']);
Route::get('users/{user}', [UserController::class, 'show']);
Route::get('users/{user}/ban', [UserController::class, 'ban']);
// Después
Route::controller(UsersController::class)->group(function () {
    Route::get('users', 'index');
    Route::post('users', 'store');
    Route::get('users/{user}', 'show');
    Route::get('users/{user}/ban', 'ban');
});

⭐ Aportación de @justsanjit