অধ্যায় ৬
The Pipeline Pattern
"Pass a value through a sequence of steps."
জটিল লজিককে ছোট ছোট স্বাধীন ধাপে ভাগ করা — HTTP middleware থেকে শুরু করে shell pipe পর্যন্ত।
আপনি আগেই ব্যবহার করেছেন
একটি কারখানায় গাড়ি তৈরি হচ্ছে। প্রথমে চেসিস বসে, তারপর ইঞ্জিন, তারপর রঙ। প্রতিটি ধাপ আলাদা এবং পর্যায়ক্রমে হয়। প্রোগ্রামিংয়েও প্রায়ই একটি ডেটাকে ধাপে ধাপে ফিল্টার বা মডিফাই করতে হয়।
লারাভেলে প্রতিটি HTTP রিকোয়েস্ট একটি middleware স্ট্যাক পার হয়: authentication, CSRF protection, rate limiting, string trimming। প্রতিটি নিজের কাজ করে রিকোয়েস্ট সামনে পাঠায়। Controller-এ পৌঁছানোর আগেই রিকোয়েস্টটি একের পর এক স্বাধীন ধাপে আকার পেয়েছে।
এটাই pipeline।
একই আইডিয়া আছে job middleware, outgoing HTTP client middleware, process piping এবং নিজের কোডে ব্যবহারের জন্য একটি general-purpose class-এ।
Pipeline কীভাবে কাজ করে
- একটি input দিয়ে শুরু (request, DTO, model, যেকোনো object)।
- সেটা একের পর এক ধাপ পার হয়।
- প্রতিটি ধাপ input বদলাতে পারে, অথবা সামনে পাঠাবে কি না ঠিক করতে পারে।
- শেষ মাথায় চূড়ান্ত ফলাফল বের হয়।
প্রতিটি ধাপ স্বাধীন — অন্যগুলো না ছুঁয়েই যোগ, বাদ, ক্রম বদল বা পুনঃব্যবহার করা যায়।
ভেতরে যা ঘটে
লারাভেল প্রতিটি “pipe” (যার handle($passable, Closure $next) মেথড আছে) array_reduce দিয়ে পরেরটার চারপাশে মুড়ে closure-এর একটি নেস্টেড চেইন বানায়। সরলীকৃত রূপ:
$destination = fn ($passable) => $passable;
$pipeline = array_reduce(
array_reverse($pipes),
fn ($next, $pipe) => fn ($passable) => (new $pipe)->handle($passable, $next),
$destination
);
$result = $pipeline($input); // সবচেয়ে বাইরের pipe থেকে শুরু
সবচেয়ে বাইরের pipe পরেরটাকে কল করে, সে তার পরেরটাকে… শেষে destination callback চলে। শেষ pipe যা রিটার্ন করে, সেটাই পুরো pipeline-এর output।
একটি সহজ উদাহরণ
use Illuminate\Pipeline\Pipeline;
$result = app(Pipeline::class)
->send($input)
->through([
CleanInput::class,
ValidateData::class,
TransformPayload::class,
])
->thenReturn();
প্রতিটি pipe দুটি আর্গুমেন্ট পায় — payload আর $next:
class CleanInput
{
public function handle($payload, Closure $next)
{
$payload['name'] = trim($payload['name']); // payload নিয়ে কাজ
return $next($payload); // পরের ধাপে পাঠানো
}
}
Isolated
প্রতিটি ক্লাস একটাই কাজ করে।
Testable
প্রতিটি pipe আলাদাভাবে টেস্ট করা যায়।
Reusable
একই pipe অন্য workflow-এ।
$next() কল না করলে pipeline সেখানেই থেমে যায় — এভাবেই auth middleware অননুমোদিত রিকোয়েস্ট আটকায়।
HTTP Middleware
public function handle(Request $request, Closure $next)
{
// ← রিকোয়েস্ট সামনে যাওয়ার আগের কাজ
$response = $next($request);
// ← রেসপন্স ফেরার পথের কাজ
return $response;
}
HTTP kernel middleware লিস্ট রিজলভ করে রিকোয়েস্টকে ক্রমানুসারে প্রতিটি স্তর দিয়ে পাঠায়। রেসপন্স উল্টো ক্রমে একই স্ট্যাক দিয়ে ফেরে, কারণ প্রতিটি স্তর পরেরটাকে মুড়ে রাখে — পেঁয়াজের খোসার মতো।
লারাভেলের ভেতরে (সরলীকৃত)
use Illuminate\Pipeline\Pipeline;
$response = (new Pipeline($app))
->send($request)
->through($middleware)
->then(function ($request) use ($route) {
return $route->run($request); // শেষে Controller
});
প্রতিটি middleware-এ যে $next($request) কল করি, সেটা আসলে pipeline-এর পরের ধাপ।
Job Middleware
Queue job-এও middleware আছে, আর গঠন HTTP middleware-এর প্রায় হুবহু:
public function middleware(): array
{
return [
new RateLimited('api'), // অধ্যায় ৫
new WithoutOverlapping($this->modelId), // অধ্যায় ৪
];
}
প্রতিটি middleware job পায় এবং সেটাকে চলতে না দেওয়া, throttle করা, lock নেওয়া, retry যোগ করা বা logging-এ মুড়ে দিতে পারে।
নিজের Job Middleware
class LogJobDuration
{
public function handle(object $job, Closure $next): void
{
$start = microtime(true);
$next($job); // job-এর handle() এখানে চলে
Log::info(get_class($job) . ' took ' . round(microtime(true) - $start, 2) . 's');
}
}
লাভ
লারাভেল HTTP-এর মতো একই pipeline ইঞ্জিনে job middleware চালায়। ফলে cross-cutting concern-গুলো job-এর handle()-এর ভেতরে চাপা পড়ে না।
HTTP Client Middleware
লারাভেলের HTTP client Guzzle-চালিত, তাই outgoing রিকোয়েস্টের pipeline Guzzle middleware দিয়ে হয়।
Outgoing রিকোয়েস্ট বদলানো
use Illuminate\Support\Facades\Http;
use Psr\Http\Message\RequestInterface;
$response = Http::withRequestMiddleware(
function (RequestInterface $request) {
return $request->withHeader('X-Example', 'Value');
}
)->get('http://example.com');
Incoming রেসপন্স দেখা
use Psr\Http\Message\ResponseInterface;
$response = Http::withResponseMiddleware(
function (ResponseInterface $response) {
$header = $response->getHeader('X-Example');
// ...
return $response;
}
)->get('http://example.com');
ব্যবহার: request/response লগ, হেডার বদলানো, টাইমিং বা ট্রেসিং, failover লজিক, circuit breaker। গঠন server-side middleware-এর মতোই — শুধু incoming-এর বদলে external কলে প্রয়োগ।
সব রিকোয়েস্টে লাগাতে চাইলে AppServiceProvider-এ Http::globalRequestMiddleware(...)।
Process Pipelines
একাধিক external command ক্রমানুসারে চালানো, যেখানে একটির output পরেরটির input — ঠিক shell-এর | এর মতো।
use Illuminate\Process\Pipe;
use Illuminate\Support\Facades\Process;
$result = Process::pipe(function (Pipe $pipe) {
$pipe->command('cat file.txt');
$pipe->command('grep "Laravel"');
});
echo $result->output();
এটা shell-এ cat file.txt | grep "Laravel" এর সমান। প্রথম command-এর stdout পরেরটায় যায়।
রিয়েল-টাইম output
দ্বিতীয় আর্গুমেন্টে callback দিন।
ধাপের নাম
ডিবাগিং-এর জন্য $pipe->as('name')।
কখন? grep, awk, sed চেইন করা, এক্সটার্নাল টুল দিয়ে ছোট ETL, অথবা Laravel কোড থেকে shell pipeline-এর composability। একই pipeline কনসেপ্ট — শুধু PHP ডেটার বদলে OS লেভেলে।
Pipeline Class — নিজের কোডে
HTTP বা queue-এর বাইরে pipeline লাগতে পারে — import প্রসেসিং, ডেটা ট্রান্সফর্ম, business rule-এর সিরিজ। ধরুন একটি Order-এ Discount, Inventory, Total — সব কন্ট্রোলারে না লিখে আলাদা ক্লাসে:
use Illuminate\Support\Facades\Pipeline;
$result = Pipeline::send($order)
->through([
ApplyDiscounts::class,
ValidateInventory::class,
RecalculateTotals::class,
])
->thenReturn();
class ApplyDiscounts
{
public function handle(Order $order, Closure $next)
{
if ($order->coupon) {
$order->total -= $order->coupon->amount;
}
return $next($order);
}
}
| মেথড | কাজ |
|---|---|
send($value) | যে ভ্যালু pipeline দিয়ে যাবে |
through([...]) | pipe-এর লিস্ট (class নাম বা closure) |
via('process') | handle-এর বদলে অন্য মেথড নাম |
then(fn) | শেষে একটি destination closure চালাও |
thenReturn() | শেষ ভ্যালুটাই রিটার্ন করো |
কখন Pipeline ব্যবহার করবেন
ভালো মানায় যখন
- লজিক নির্দিষ্ট ক্রমে প্রয়োগ করতে হবে
- প্রতিটি ধাপ আলাদা ও testable
- পরে ধাপ যোগ/বাদ দেওয়ার সম্ভাবনা
- একাধিক workflow-এ ধাপ পুনঃব্যবহার
দরকার নেই যখন
- মাত্র ২–৩ লাইনের লজিক — সাধারণ loop বা মেথড কলই যথেষ্ট
- ধাপগুলো একে অপরের উপর জটিলভাবে নির্ভরশীল
বাস্তব প্রজেক্টের উদাহরণ
CSV Import
sanitize → validate → column map → store
Business Rules
domain object-এ নিয়মের সিরিজ
Uploaded File
resize → watermark → optimize
User Content
strip HTML → mention → link → spam check
Pipeline সবকিছু করা একটি বিশাল service class-এর বদলে ছোট, উদ্দেশ্য-নির্দিষ্ট ক্লাসে কাজ ঠেলে দেয়। প্রতিটি ধাপের একটাই কাজ আর পরিষ্কার interface থাকলে workflow টেস্ট, বদলানো ও পড়া সহজ হয়।
পুরো লারাভেলে Pipeline
| কোথায় | কী পাস হয় | API |
|---|---|---|
| HTTP Middleware | Incoming Request | handle($request, $next) |
| Job Middleware | Queue Job | middleware(): array |
| HTTP Client | Outgoing Request / Response | withRequestMiddleware() |
| Process | stdout → stdin | Process::pipe() |
| Pipeline Class | যেকোনো ভ্যালু | Pipeline::send()->through() |
মূল কথা
Incoming request, outgoing HTTP call, background job, shell command — সব একই কাঠামোগত আইডিয়া ব্যবহার করে। HTTP middleware কীভাবে কাজ করে বুঝলে job ও outgoing request-এরটাও বুঝে গেছেন।
সারসংক্ষেপ ও যাচাই
- Pipeline = একটি ভ্যালু একের পর এক স্বাধীন ধাপে পাস করা।
- প্রতিটি pipe:
handle($passable, Closure $next)এবংreturn $next($passable)। - ভেতরে
array_reduceদিয়ে closure-এর নেস্টেড চেইন — তাই রেসপন্স উল্টো ক্রমে ফেরে। - HTTP, Job, HTTP Client, Process আর
Pipelineclass — সব একই আকার।
নিজেকে যাচাই করুন
Middleware-এ $next($request) কল না করে সরাসরি response রিটার্ন করলে কী হবে?
Pipeline সেখানেই থামবে — পরের middleware বা controller চলবে না। Auth middleware এভাবেই redirect করে।
A, B, C middleware — রেসপন্সের “পরের কাজ” কোন ক্রমে চলবে?
C → B → A। প্রতিটি স্তর পরেরটাকে মুড়ে রাখে, তাই ফেরার পথ উল্টো।
then() আর thenReturn()-এর পার্থক্য?
then() শেষে একটি destination closure চালায় (যেমন controller); thenReturn() শুধু শেষ ভ্যালুটা ফেরত দেয়।