เรามี append กับ prepend ไว้เพิ่ม middleware ใน bootstrap/app.php มาตั้งแต่ Laravel 11 สองตัวนี้วางได้แค่หัวกับท้ายของ stack แต่บางงานต้องอยู่ตรงกลางพอดี
ลำดับกำหนดว่า middleware แต่ละตัวเห็นอะไรได้บ้าง
web group เรียงตามลำดับนี้
EncryptCookiesAddQueuedCookiesToResponseStartSessionShareErrorsFromSessionPreventRequestForgerySubstituteBindings
ตัวที่ 3 เปิดให้อ่าน session ได้ ส่วนตัวที่ 6 คือจังหวะที่ Laravel แปลงค่าใน URL เป็น model ถ้า middleware ของเราต้องอ่าน session และต้องส่งผลกับการหา model ด้วย ก็ต้องอยู่ระหว่างตัวที่ 3 กับตัวที่ 6
เว็บสองภาษาต้องตั้ง locale ก่อน Laravel หา model
สมมติเว็บขายของออนไลน์มีทั้งหน้าไทยและอังกฤษ เก็บ slug ของสินค้าแยกตามภาษาใน JSON แล้วให้ route model binding หาตามภาษาปัจจุบัน
public function resolveRouteBinding($value, $field = null)
{
return $this->where('slug->'.app()->getLocale(), $value)->firstOrFail();
}
ทีนี้ลองเพิ่ม middleware ที่อ่านภาษาจาก session ด้วยวิธีเดิมสองแบบ
web(append: …)ทำงานหลังSubstituteBindingsซึ่งหาสินค้าด้วยภาษาเริ่มต้นไปแล้ว คนที่เลือกภาษาอื่นจึงเจอ 404web(prepend: …)ทำงานก่อนStartSessionแล้วพังด้วยSession store not set on request.
ที่ผ่านมาเราก็เลยต้องเขียน web group ใหม่ทั้งชุดด้วย $middleware->group() ก็ใช้ได้นะ แต่ชุดที่ก็อปไว้จะไม่ตาม framework เช่น Laravel 12 ใช้ ValidateCsrfToken ส่วน 13 เปลี่ยนเป็น PreventRequestForgery และ auth.session จาก authenticateSessions() ก็จะหายไปด้วย
insertAfterInGroup() แทรกเข้ากลาง group ได้เลย
use App\Http\Middleware\SetLocaleFromSession;
use Illuminate\Session\Middleware\StartSession;
->withMiddleware(function (Middleware $middleware) {
$middleware->insertAfterInGroup('web', StartSession::class, SetLocaleFromSession::class);
})
public function handle(Request $request, Closure $next): Response
{
if ($locale = $request->session()->get('locale')) {
App::setLocale($locale);
}
return $next($request);
}
บรรทัดเดียวนี้ทำให้ middleware ไปอยู่ถัดจาก StartSession ส่วนตัวอื่นยังเป็นไปตามที่ framework กำหนด อัปเกรด Laravel รอบหน้าเราก็ไม่ต้องตามแก้
| เมธอด | ใช้กับ | ลำดับอาร์กิวเมนต์ |
|---|---|---|
insertBefore(), insertAfter() |
global stack | ตัวที่หา, ตัวที่จะแทรก |
insertBeforeInGroup(), insertAfterInGroup() |
group ใดก็ได้ รวมถึง group ที่สร้างเอง | ชื่อ group, ตัวที่หา, ตัวที่จะแทรก |
ตัวที่จะแทรกส่งเป็น array ได้ ถ้ามีหลายตัว
Middleware ที่ใช้ IP ผู้ใช้ต้องรันหลัง TrustProxies
global stack ก็เจอแบบเดียวกัน ถ้าเว็บอยู่หลัง Cloudflare หรือ load balancer แล้วเราจะบล็อก IP ที่ติด blacklist
prepend()รันก่อนTrustProxiesซึ่ง$request->ip()ยังเป็น IP ของ proxyappend()ได้ IP ถูกแล้ว แต่รันหลังHandleCors, ตัวเช็ค maintenance mode และตัวจัดการ input
insertAfter() วางไว้ถัดจาก TrustProxies พอดี
use App\Http\Middleware\BlockBannedIps;
use Illuminate\Http\Middleware\TrustProxies;
$middleware->insertAfter(TrustProxies::class, BlockBannedIps::class);
เรื่องนี้ต้องตั้ง trustProxies() ให้ถูกก่อนด้วย ไม่อย่างนั้น IP ที่ได้ก็ยังเชื่อไม่ได้ อ่านวิธีตั้งได้ที่ตั้ง trustProxies ให้ Laravel ที่อยู่หลัง Cloudflare
ส่วน insertBefore() ใช้แบบเดียวกัน เช่นทำความสะอาด input ก่อน TrimStrings
use App\Http\Middleware\SanitizeInput;
use Illuminate\Foundation\Http\Middleware\TrimStrings;
$middleware->insertBefore(TrimStrings::class, SanitizeInput::class);
replace() รับ array ได้แล้ว
13.40 ให้ replace() กับ replaceInGroup() แทนที่ตัวเดียวด้วยหลายตัวได้ เหมาะเมื่อจะเปลี่ยนตัวเดิมเป็นสองตัวใหม่
$middleware->replace(Foo::class, [Bar::class, Baz::class]);
ถ้าแค่อยากแทรก ใช้ insert… จะอ่านตรงกว่าการใส่ตัวเดิมกลับเข้าไปใน array
Laravel ข้ามไปเงียบ ๆ เมื่อหาตัวที่ระบุไม่เจอ
เมธอดกลุ่มนี้ไม่มีขั้นตอนของตัวเอง โค้ดของ Laravel เก็บไว้เป็นรายการ replace แล้วค่อยไล่เทียบกับ stack ตอนประกอบ ผลที่ตามมาคือ
- หาไม่เจอก็ไม่มี error middleware ที่จะแทรกจะไม่เข้า stack เลย
- ต้องตรงทุกตัวอักษร ชื่อ class เทียบกับ alias อย่าง
auth.sessionไม่ได้ และ subclass ก็ไม่นับเป็น parent - ใช้ตัวที่เพิ่งเพิ่มเป็นจุดอ้างอิงไม่ได้ ตัวที่มาจาก
append,prependหรือinsert…อื่นจะหาไม่เจอinsertAfter(A, B)ต่อด้วยinsertAfter(B, C)จึงไม่มีCใน stack ให้เขียนเป็นinsertAfter(A, [B, C])แทน replace()ที่เรียกทีหลังจะทับ การแทรกก่อนหน้าที่อ้างตัวเดียวกัน
วิธีเช็คคือเปิดดู stack ของ route จริงหลังแก้ทุกครั้ง
php artisan route:list -vv --path=products
-vv กาง group ออกให้เห็นทีละตัว แต่ไม่แสดง global stack ถ้าแทรกในระดับ global ให้เขียน feature test ยิง request ดูผลแทน เช่นยิงจาก IP ที่บล็อกแล้วต้องได้ 403
ฟีเจอร์นี้ต้องใช้ Laravel 13.40 ขึ้นไป ตัวอย่างและรายละเอียดเพิ่มเติมอยู่ในโพสต์ต้นฉบับของ Laravel News ครับ






