Skip to content

appendToPriorityList() appends to the end of the list when the referenced middleware is first #61536

Closed

Description

@shaxzodbe

Laravel Version

13.31.0

PHP Version

8.4.25

Database Driver & Version

No response

Description

Adding middleware after another entry in the priority list puts it at the end of the list instead, but only when the referenced middleware is the first entry.

->withMiddleware(function (Middleware $middleware) {
    $middleware->appendToPriorityList(
        HandlePrecognitiveRequests::class,   // the first entry of the default priority list
        EnsureTenantIsSet::class,
    );
})

EnsureTenantIsSet ends up last in the priority list rather than second, so it runs after authentication, throttling and session handling instead of right after the entry it was placed behind.

Kernel::addToMiddlewarePriorityRelative() starts the insertion point at 0 and only moves it when the match sits further along:

$index = $after ? 0 : count($this->middlewarePriority);

foreach ((array) $existing as $existingMiddleware) {
    if (in_array($existingMiddleware, $this->middlewarePriority)) {
        $middlewareIndex = array_search($existingMiddleware, $this->middlewarePriority);

        if ($after && $middlewareIndex > $index) {
            $index = $middlewareIndex + 1;
        } elseif ($after === false && $middlewareIndex < $index) {
            $index = $middlewareIndex;
        }
    }
}

if ($index === 0 && $after === false) {
    array_unshift($this->middlewarePriority, $middleware);
} elseif (($after && $index === 0) || $index === count($this->middlewarePriority)) {
    $this->middlewarePriority[] = $middleware;
} else {
    array_splice($this->middlewarePriority, $index, 0, $middleware);
}

For a match at position 0 the guard is 0 > 0, which is false, so $index stays at its initial value. The branch below then reads $after && $index === 0 as "the referenced middleware was not found" and appends. 0 is serving both as the starting value and as a real position, and the two cases are indistinguishable by the time the branch runs.

Starting from [A, B, C, D]:

call expected actual
addToMiddlewarePriorityAfter('A', 'X') A, X, B, C, D A, B, C, D, X
addToMiddlewarePriorityAfter('B', 'X') A, B, X, C, D A, B, X, C, D
addToMiddlewarePriorityAfter('D', 'X') A, B, C, D, X A, B, C, D, X
addToMiddlewarePriorityAfter(['A', 'Missing'], 'X') A, X, B, C, D A, B, C, D, X
addToMiddlewarePriorityBefore('A', 'X') X, A, B, C, D X, A, B, C, D
addToMiddlewarePriorityBefore('D', 'X') A, B, C, X, D A, B, C, X, D

Only "after the first entry" is affected. Every other position is placed correctly, and addToMiddlewarePriorityBefore() is correct throughout, including for the first entry, because its starting value is count($this->middlewarePriority) rather than a reachable position.

There is no error or warning; the middleware is registered, just in a different place than asked for.

Steps To Reproduce

composer require laravel/framework
<?php

require 'vendor/autoload.php';

use Illuminate\Foundation\Http\Kernel;

class ProbeKernel extends Kernel
{
    public function __construct() {}

    protected function syncMiddlewareToRouter(): void {}

    public function setPriority(array $priority): void
    {
        $this->middlewarePriority = $priority;
    }

    public function priority(): array
    {
        return $this->middlewarePriority;
    }
}

$first = new ProbeKernel;
$first->setPriority(['A', 'B', 'C']);
$first->addToMiddlewarePriorityAfter('A', 'X');

$second = new ProbeKernel;
$second->setPriority(['A', 'B', 'C']);
$second->addToMiddlewarePriorityAfter('B', 'X');

var_dump($first->priority(), $second->priority());

Expected: ['A', 'X', 'B', 'C'] and ['A', 'B', 'X', 'C'].

Actual: ['A', 'B', 'C', 'X'] and ['A', 'B', 'X', 'C'].

The subclass only exists to keep the reproduction short — it skips the constructor and the router sync, and does not touch the method under test. The same happens through appendToPriorityList() in bootstrap/app.php, which reaches addToMiddlewarePriorityAfter() via ApplicationBuilder.

Activity

  1. locked and limited conversation to collaborators on Sep 11, 2026
  2. converted this issue into a discussion #61546 on Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions