Некоторые middleware должны выполняться в определенной точке стандартного стека Laravel. Middleware, которое считывает IP-адрес клиента, должно выполняться после  TrustProxies . Middleware, считывающее сессию, должно выполняться после  StartSession , а middleware, изменяющее разрешение моделей маршрутов — перед  SubstituteBindings .

Колбэк  withMiddleware()  в  bootstrap/app.php  позволял добавлять middleware в начало или конец глобального стека и группы, но не рядом с конкретным встроенным middleware. В Laravel 13.40 добавлены четыре метода для этих целей, а также появилась возможность передавать массив в существующие методы  replace() .

#Вставка рядом со встроенным middleware

Методы  insertBefore()  и  insertAfter()  работают с глобальным стеком middleware. Первый аргумент — это middleware, которое нужно найти, а второй — одно middleware или массив таковых:

use App\Http\Middleware\BlockBannedIps;
use App\Http\Middleware\SanitizeInput;
use Illuminate\Foundation\Http\Middleware\TrimStrings;
use Illuminate\Http\Middleware\TrustProxies;

->withMiddleware(function (Middleware $middleware) {
    $middleware->insertAfter(TrustProxies::class, BlockBannedIps::class);

    $middleware->insertBefore(TrimStrings::class, SanitizeInput::class);
})

Методы  insertBeforeInGroup()  и  insertAfterInGroup()  делают то же самое внутри группы middleware. Первым аргументом они принимают имя группы:

use App\Http\Middleware\SetLocaleFromSession;
use Illuminate\Session\Middleware\StartSession;

->withMiddleware(function (Middleware $middleware) {
    $middleware->insertAfterInGroup('web', StartSession::class, SetLocaleFromSession::class);
})

Эти методы работают с любыми группами, включая те, которые вы определяете с помощью  $middleware->group() .

#Замена с помощью массива

Новые методы основаны на небольшом изменении от того же автора. Теперь методы  replace()  и  replaceInGroup()  принимают массив, который заменяет исходное middleware в стеке. Чтобы выполнить вставку рядом с определенным middleware, включите его в массив:

// Run StripCrossOriginReferer after TrustProxies
$middleware->replace(TrustProxies::class, [
    TrustProxies::class,
    StripCrossOriginReferer::class,
]);

// Replace one middleware with two
$middleware->replace(Foo::class, [Bar::class, Baz::class]);

Хелперы  web()  и  api()  передают свой аргумент  replace  в  replaceInGroup() , поэтому массивы работают и там:

$middleware->web(replace: [
    StartSession::class => [StartSession::class, SetLocaleFromSession::class],
]);

#Пример: локаль из сессии

Некоторые приложения сохраняют язык пользователя в сессии и переводят URL-слаги. Маршрут  /fr/articles/{article}  может разрешать статью по ее французскому слагу с помощью модели, которая считывает текущую локаль в  resolveRouteBinding() :

class Article extends Model
{
    public function resolveRouteBinding($value, $field = null)
    {
        return $this->where('slug->'.app()->getLocale(), $value)->firstOrFail();
    }
}

Локаль должна устанавливаться после запуска сессии и до того, как  SubstituteBindings  разрешит модель. Стандартная группа  web  выполняется в следующем порядке:

  1.  EncryptCookies 
  2.  AddQueuedCookiesToResponse 
  3.  StartSession 
  4.  ShareErrorsFromSession 
  5.  PreventRequestForgery 
  6.  SubstituteBindings 

До версии 13.40 ни один хелпер не помещал middleware в нужное место. С  web(append: ...)  middleware выполняется после  SubstituteBindings , поэтому поиск модели происходит с локалью по умолчанию, и французский слаг возвращает 404. С  web(prepend: ...)  оно выполняется перед  StartSession , и  $request->session()  выбрасывает  Session store not set on request. 

Временным решением было переопределение всей группы целиком:

$middleware->group('web', [
    EncryptCookies::class,
    AddQueuedCookiesToResponse::class,
    StartSession::class,
    SetLocaleFromSession::class,
    ShareErrorsFromSession::class,
    PreventRequestForgery::class,
    SubstituteBindings::class,
]);

Такая копия не учитывает изменения в фреймворке. В Laravel 12 в этой группе использовался  ValidateCsrfToken , а в Laravel 13 используется  PreventRequestForgery , поэтому приложение, скопировавшее группу из Laravel 12, сохранило бы старый класс после обновления. Скопированный список также теряет  auth.session , который Laravel добавляет в группу при вызове  $middleware->authenticateSessions() .

Начиная с версии 13.40, вам достаточно указать только добавляемое middleware:

->withMiddleware(function (Middleware $middleware) {
    $middleware->insertAfterInGroup('web', StartSession::class, SetLocaleFromSession::class);
})

Это middleware считывает сессию и устанавливает локаль:

class SetLocaleFromSession
{
    public function handle(Request $request, Closure $next): Response
    {
        if ($locale = $request->session()->get('locale')) {
            App::setLocale($locale);
        }

        return $next($request);
    }
}

В результате получается следующая группа:  EncryptCookies ,  AddQueuedCookiesToResponse ,  StartSession ,  SetLocaleFromSession ,  ShareErrorsFromSession ,  PreventRequestForgery ,  SubstituteBindings .

#Пример: IP-адрес клиента

Глобальный стек имеет ту же проблему. Middleware, проверяющее  $request->ip()  по списку заблокированных адресов, получит адрес балансировщика нагрузки, если оно выполняется перед  TrustProxies , а  prepend()  помещает его перед  TrustProxies . С помощью  append()  оно запускается после  HandleCors , проверки режима обслуживания и middleware для входных данных.

Вместо этого выполните вставку сразу после  TrustProxies :

$middleware->insertAfter(TrustProxies::class, BlockBannedIps::class);
class BlockBannedIps
{
    public function handle(Request $request, Closure $next): Response
    {
        if (in_array($request->ip(), config('security.banned_ips'), true)) {
            abort(403);
        }

        return $next($request);
    }
}

#Дополнительное чтение

Джейми Йорк (Jamie York) добавил поддержку массивов для  replace()  в пулреквесте #62230 и методы вставки в #62261.