পড়ে মুখস্থ করলে কাজ হবে না। এই ছয়টা করলে হবে।
সময় কম থাকলে এই ক্রমে পড়ো: §৫ System design → §২ Laravel internals → §৩ Database → §৯ Behavioural। বাকিগুলো তুমি এমনিতেই জানো, শুধু ঝালাই দিলেই হবে।
Zend certification আছে বলে এখানে একটু বেশি গভীরে জিজ্ঞেস করবে। প্রস্তুত থেকো।
$this, self আর static?$this মানে এই object-টা। self মানে যেই class-এর ভিতরে লাইনটা লেখা আছে, সেই class। আর static মানে যেই class থেকে ডাকা হয়েছে সেটা — child থেকে ডাকলে child-কেই বোঝাবে।new self() লিখি, তাহলে child থেকে ডাকলেও parent-এর object-ই বানাবে, যেটা আমি চাইনি। new static() লিখলে যেই class থেকে ডেকেছি তারটাই বানায়। Laravel-এ Model::query() এইভাবেই সঠিক model ফেরত দিতে পারে।__get চলে, method না থাকলে __call। Laravel-এর Facade আসলে __callStatic দিয়ে বানানো, আর Eloquent-এ $user->name কাজ করে __get দিয়ে।match, named argument আর attribute। এর মধ্যে attribute দিয়ে আমি নিজের framework-এ routing বানিয়েছি — controller-এর method-এর উপরেই route লিখে দেওয়া যায়।match আর switch-এর পার্থক্য?match কঠোরভাবে মেলায়, মানে === দিয়ে — তাই "1" আর 1 এক না। এটা সরাসরি একটা মান ফেরত দেয়, break লিখতে হয় না, আর কোনো case না মিললে চুপ করে না থেকে error দেয়। এই শেষ জিনিসটার জন্যই আমি match পছন্দ করি — ভুল চুপচাপ পার পায় না।'pending', 'paid' এইভাবে string লিখতাম। বানান ভুল হলে ধরা পড়ত অনেক পরে, DB-তে গিয়ে। Enum দিয়ে মানগুলো নির্দিষ্ট করা থাকে, ভুল লিখলে সাথে সাথে error দেয় আর editor-ও suggest করে।yield কেন লাগে?yield দিয়ে একটা একটা করে দেওয়া যায় — দশ লাখ লাইনের file পড়লেও memory একই থাকে, কারণ একসাথে একটা লাইনই ধরা থাকে।== আর ===?== type বদলে তুলনা করে, তাই "0" আর false এক হয়ে যায়। === type সহ মেলায়। আমি সবসময় === ব্যবহার করি, কারণ এই ধরনের bug খুঁজে বের করা সবচেয়ে কঠিন — কোথাও error দেয় না, শুধু ভুল ফল দেয়।use দিয়ে। সাধারণভাবে একটা copy যায়, আর & দিলে আসলটাই যায়। এছাড়া Closure::bind দিয়ে closure-এর ভিতরে $this কে বদলে দেওয়া যায় — Laravel অনেক জায়গায় এটা করে।"Laravel ব্যবহার করতে পারি" আর "Laravel কীভাবে কাজ করে জানি" — এই section-টাই দুইটার পার্থক্য।
public/index.php-তে আসে। ওখানে autoload হয়, তারপর application তৈরি হয়ে HTTP Kernel-এ যায়। Kernel প্রথমে env আর config load করে, exception handler বসায়, তারপর সব service provider আগে register করে পরে boot করে। এরপর request middleware-গুলোর ভিতর দিয়ে যায়, route মেলে, controller চলে। Controller যেই response দেয় সেটা আবার middleware-এর উল্টো পথে ফিরে user-এর কাছে যায়, আর সবার শেষে terminate middleware চলে — যেমন session লেখা।
এই একটা উত্তর ভাল দিলে interviewer ধরে নেয় তুমি Laravel সত্যিই চেনো। এইটা মুখস্থ করে ফেলো।new লিখে dependency জোগাড় করতে হয় না।bind আর singleton?bind করলে যতবার চাইব ততবার নতুন object পাব। singleton করলে প্রথমবার একটা বানিয়ে রেখে দেয়, এরপর সবাই ওই একটাই পায়। যেগুলো বানাতে খরচ বেশি বা যেগুলোর ভিতরে কিছু জমিয়ে রাখতে হয়, সেগুলো singleton করি।register() আর boot()-এর পার্থক্য?register()-এ আমি শুধু container-এ জিনিস bind করি, অন্য কোনো service ছুঁই না — কারণ তখনও সব provider register হয়নি, ওটা তখন নাও থাকতে পারে। boot() চলে সব register হয়ে যাওয়ার পর, তাই ওখানে অন্য service ব্যবহার করা নিরাপদ। Event listener, view composer, route — এগুলো boot-এ যায়।Cache::get() লিখলে PHP __callStatic ধরে, সেটা container থেকে আসল cache object-টা বের করে আনে, তারপর তার উপর get() চালায়। কোন object আনবে সেটা getFacadeAccessor() বলে দেয়।$next($request) ডাকে, তাই request ভিতরের দিকে যেতে থাকে। Controller-এর response আবার উল্টো পথে সবার ভিতর দিয়ে ফিরে আসে। এই কারণেই একই middleware-এ request-এর আগেও কাজ করা যায়, response তৈরি হওয়ার পরেও।auth বা throttle। ভারী কোনো কাজ আমি কখনো global-এ দিই না, কারণ তাহলে যেই request-এর দরকার নেই সেটাও ধীর হয়ে যায়।hasOne আর hasMany — এক জিনিসের নিচে এক বা একাধিক। belongsTo তার উল্টো দিক। belongsToMany যখন দুই দিকেই অনেক, মাঝে একটা pivot table থাকে। আর polymorphic — যেমন comment, সেটা post-এও থাকতে পারে video-তেও, তখন এক টেবিলেই দুই জায়গার comment রাখা যায়।with('author') দিলে দুইটা query-তেই শেষ। ধরার জন্য Telescope বা Debugbar দিয়ে query count দেখি।
তুমি নিজেই eloquent-pulse নামে একটা VS Code extension বানিয়েছ যেটা কোড লেখার সময়ই query-গুলো দেখায় — এই প্রশ্নের উত্তরে ওটা বলবে, খুব ভাল লাগবে।with() আর load()?with() query চালানোর আগে বলে দেয় কী কী একসাথে আনতে হবে। load() ব্যবহার করি যখন data ইতিমধ্যে হাতে চলে এসেছে, তারপর মনে হলো relation-টাও লাগবে।all() কখনো করি না, সব memory-তে উঠে যাবে। chunkById() দিয়ে হাজার হাজার করে ভাগ করে নিই, অথবা cursor() বা lazy() দিয়ে একটা একটা করে। chunkById বেশি নিরাপদ কারণ মাঝখানে data বদলালেও row বাদ পড়ে না।tries দিয়ে ঠিক করি, আর দুই চেষ্টার মাঝে কত সময় থামবে সেটা backoff। সব চেষ্টা শেষ হলে job টা failed_jobs table-এ চলে যায়, পরে দেখা বা আবার চালানো যায়। আর failed() method-এ আমি alert পাঠাই বা যা আধা-কাজ হয়েছিল সেটা পরিষ্কার করি।Bus::batch।config:cache-এর পর env() কাজ করে না কেন?.env file পড়ে না, একটা তৈরি করা file থেকে config নেয়। তাই controller বা service-এ env() লিখলে সেটা null আসবে আর production-এ হঠাৎ জিনিস ভেঙে যাবে। এই কারণে আমি env() শুধু config folder-এর ভিতরে ব্যবহার করি, আর কোথাও না।where টা query-তেই দেওয়া উচিত ছিল। এই ভুলটা নতুনদের code-এ আমি প্রায়ই দেখি।scopeActive। Global scope নিজে থেকেই প্রতিটা query-তে জুড়ে যায় — soft delete আর multi-tenant এইভাবেই কাজ করে। এর সুবিধা হলো কেউ লিখতে ভুলে গেলেও data মিশে যায় না।Senior interview-এ index আর transaction — এই দুইটা প্রায় নিশ্চিত আসবে।
a, b, c দিয়ে একটা index বানালে সেটা কাজ করে যদি where-এ a থাকে, বা a আর b, বা তিনটাই। কিন্তু শুধু b দিয়ে খুঁজলে index কাজে লাগে না, কারণ সাজানোটা হয়েছে আগে a ধরে — ঠিক ফোনবুকের মত, নাম জানলে খুঁজে পাবে, শুধু পদবি জানলে না।
এই প্রশ্নটা প্রায় সব senior interview-এ আসে। ফোনবুকের উদাহরণটা দিলে interviewer সাথে সাথে বোঝে তুমি জিনিসটা বুঝে বলছ।EXPLAIN-এ কী দেখো?type — ওখানে ALL লেখা থাকলে বুঝি পুরো table পড়ছে, ওটাই সমস্যা। তারপর key দেখি কোন index লেগেছে, আর rows দেখি কত row ছুঁচ্ছে। শেষে Extra-তে Using filesort বা Using temporary দেখলে বুঝি order by বা group by index কাজে লাগাতে পারছে না।EXPLAIN চালাই, তারপর দেখি where আর order by-র column-এ index আছে কিনা। SELECT * থাকলে দরকারি column-এ নামিয়ে আনি। একটা ভুল আমি প্রায়ই দেখি — where-এ column-এর উপর function লাগানো, যেমন DATE(created_at) = ...; ওতে index কাজ করে না, তাই ওটা দুইটা তারিখের মাঝের range-এ ভেঙে দিই। এতেও না হলে তখন cache বা আগে থেকে হিসাব করে রাখার কথা ভাবি।
তোমার ২০ সেকেন্ড থেকে ৩০০ মিলিসেকেন্ডে নামানোর গল্পটা ঠিক এইখানে বলবে।is_active — সেখানে index দিয়ে লাভ নেই, কারণ অর্ধেক table এমনিতেই মিলে যায়। আর যেই table-এ পড়ার তুলনায় লেখা অনেক বেশি, সেখানে বেশি index দিলে লেখাটাই ধীর হয়ে যায়।SELECT ... FOR UPDATE দিয়ে row টা lock করে ফেলি, অন্য কেউ ছুঁতে পারে না। Optimistic হলো ধরে না রেখে একটা version column রাখা; লেখার সময় মিলিয়ে দেখি মাঝখানে কেউ বদলেছে কিনা, বদলে থাকলে আবার চেষ্টা করি। টাকা বা সিটের মত জায়গায় pessimistic নিই, আর যেখানে সংঘর্ষ কম সেখানে optimistic।LIMIT 100000, 20 লিখলে DB আসলে এক লাখ বিশটা row পড়ে, তারপর প্রথম এক লাখ ফেলে দেয় — তাই যত পিছনের page তত ধীর। বড় list-এ আমি cursor pagination ব্যবহার করি, মানে শেষ id মনে রেখে where id > ওই id দিয়ে পরের বিশটা আনা। তখন প্রথম page আর হাজারতম page একই গতিতে আসে।deleted_at is null যোগ হয়, তাই index-এও deleted_at রাখতে হয়, নাহলে ধীর হয়ে যায়। দুই, unique constraint ভেঙে যায় — মুছে ফেলা email এখনো table-এ পড়ে আছে, তাই একই email দিয়ে আর account খোলা যায় না। এটা সামলাতে হয় unique-এ deleted_at যোগ করে।/orders, /orders/5। কী করছি সেটা HTTP method বলে দেয়, তাই /getOrders লেখার দরকার নেই। আর status code ঠিকভাবে দিই, কারণ frontend ওটার উপর ভিত্তি করেই সিদ্ধান্ত নেয়।/api/v1। দেখলেই বোঝা যায়, cache করা সহজ, আর পুরনো mobile app গুলো v1-এ চলতে থাকে যখন নতুনটা v2-তে চলে যায়। Header দিয়েও করা যায়, দেখতে সুন্দর, কিন্তু debug করা কষ্ট।throttle middleware দিয়ে, গোনাটা Redis-এ হয়। User login করা থাকলে user ধরে গুনি, না থাকলে IP ধরে। আর যেই endpoint ভারী — যেমন search বা report — সেখানে আলাদা করে কম সীমা দিই।l5-swagger package ব্যবহার করে — code-এর সাথেই থাকে বলে সহজে পুরনো হয়ে যায় না। সাথে একটা Postman collection দিয়ে রাখি, যেন frontend-এর লোক সরাসরি চালিয়ে দেখতে পারে।এইটাই senior-এর আসল পরীক্ষা। ওরা কোড দেখবে না, তুমি কীভাবে ভাবো সেটা দেখবে।
৭ ধাপের নিয়ম — যেকোনো design প্রশ্নে এই ক্রমেই যাবে
যেই ৫টা প্রশ্ন সবচেয়ে বেশি আসে — প্রতিটার উত্তর মুখে বলার মতো করে।
tenant_id দিয়ে আলাদা, বা প্রতি tenant-এর আলাদা schema, বা একদম আলাদা DB। আমি সাধারণত প্রথমটা নিই, কারণ খরচ কম আর migration একবারই চালাতে হয়। তবে এর সবচেয়ে বড় ঝুঁকি হলো কোথাও tenant_id দিতে ভুলে যাওয়া — তাই আমি global scope দিয়ে ওটা বাধ্যতামূলক করে দিই, হাতে লিখতে হয় না। আর cache key-তেও tenant id ঢোকাতে হয়, নাহলে এক tenant আরেকজনের cache করা data দেখে ফেলবে।
এইটা তুমি সত্যিই বানিয়েছ, per-tenant cache invalidation সহ। শেষ লাইনটা বলার পর "এই জিনিসটা আমি নিজে ভুগে শিখেছি" যোগ করলে উত্তরটা আরও বিশ্বাসযোগ্য হয়।এইগুলো এক লাইনে বলতে পারলেই যথেষ্ট। Laravel কীভাবে সামলায় সেটা বলাই আসল উত্তর।
| ঝুঁকি | তুমি যা বলবে |
|---|---|
| SQL Injection | Eloquent আর Query Builder নিজেই binding ব্যবহার করে, তাই এমনিতে নিরাপদ। ঝুঁকি তৈরি হয় DB::raw-তে হাতে string জোড়া দিলে — ওখানে আমি সবসময় binding দিই। |
| XSS | Blade-এর {{ }} নিজেই escape করে দেয়। {!! !!} শুধু তখনই ব্যবহার করি যখন HTML-টা আমার নিজের, user-এর দেওয়া কিছুতে কখনো না। |
| CSRF | Form-এ @csrf token, আর middleware সেটা মিলিয়ে দেখে। API-তে token ভিত্তিক auth থাকলে এটা লাগে না। |
| Mass assignment | $fillable দিয়ে বলে দিই কোন field গুলো বাইরে থেকে আসতে পারে। ওটা খোলা রেখে $request->all() দিলে কেউ is_admin পাঠিয়ে দিতে পারে। |
| Access control (IDOR) | Login করা মানেই সব দেখার অধিকার না। /orders/5 এলে দেখি ওটা আসলেই এই user-এর কিনা — Policy দিয়ে করি, তাহলে সব জায়গায় একই নিয়ম থাকে। |
| File upload | mime আর size যাচাই করি, file-এর নাম নিজে বানাই, আর public folder-এর বাইরে রাখি। দেখাতে হলে signed URL দিই। |
| Sensitive data | Password bcrypt বা argon2-তে, .env কখনো repo-তে না, আর log-এ token বা card number যাতে না যায় সেটা দেখি। |
| SSRF | User-এর দেওয়া URL-এ server থেকে সরাসরি request মারি না, whitelist করে নিই। |
| Dependency | composer audit চালাই। পুরনো package-ই সবচেয়ে সহজ ফাঁক, আর ওটা নিজের code দেখে ধরা যায় না। |
require_once দিয়ে load হচ্ছিল, কিন্তু ওই file গুলো আসলে একটা array return করে। require_once-এর নিয়ম হলো, দ্বিতীয়বার ডাকলে সে শুধু true ফেরত দেয়। তাই এক process-এ দ্বিতীয়বার application boot করলে config-এর জায়গায় true চলে আসত। ব্যবহারে কেউ ধরতে পারত না, কারণ একটা request-এ একবারই boot হয় — কিন্তু test একই process-এ বারবার boot করে, তাই সাথে সাথে ধরা পড়ল।
getallheaders() কোনো check ছাড়াই ডাকা ছিল। ওই function শুধু Apache আর FPM-এ থাকে, তাই command line থেকে চালালে framework-টা একদম fatal হয়ে যেত।
.env-এ, আর CI-তে ওদের নিজস্ব secret store-এ। Repo-তে কখনো না। এই ধরনের জিনিস আমি একবার ভুগে শিখেছি — একটা repo-তে ভুল git identity দিয়ে কয়েকটা commit হয়ে গিয়েছিল, push করার আগেই ধরে ঠিক করেছি। তারপর থেকে প্রতিটা repo-তে আলাদা করে local config দিয়ে দিই, global-এর উপর ভরসা করি না।উত্তর দেবে STAR-এ — পরিস্থিতি, তোমার কাজ, সিদ্ধান্ত, ফল। প্রতিটা ২ মিনিট। "আমরা" না, "আমি" বলবে।
⚠ Salary — এইখানে ভুল করলে বাকি প্রস্তুতি বৃথা
⚠ Notice period — "immediate" লিখিতভাবে দেওয়ার আগে নিজের contract একবার দেখে নাও। বলার পর মিলল না, সেটা খারাপ দেখায়।
এই দশটা তৈরি থাকলে প্রায় সব behavioural আর "আপনি কী করেছেন" প্রশ্ন কভার হয়ে যায়। প্রতিটা ২ মিনিটে, সংখ্যা সহ বলার practice করো।
| # | গল্প | কোন প্রশ্নে লাগবে |
|---|---|---|
| ১ | নিজের PHP framework — container, auth, migration engine, console command | "গভীরতা আছে?" · "framework ভিতরে কীভাবে কাজ করে?" |
| ২ | Test চালিয়ে দুইটা আসল bug ধরা (§৭) | "test করে লাভ কী?" · quality |
| ৩ | Query ২০ সেকেন্ড থেকে ৩০০ মিলিসেকেন্ড | "performance সমস্যা solve করেছেন?" |
| ৪ | Multi-tenant SaaS, per-tenant cache invalidation | scale · architecture |
| ৫ | Subscription tier — interface আর handler দিয়ে, if-else দিয়ে না | SOLID · "নতুন plan যোগ করা কত সহজ?" |
| ৬ | Apple আর Google login শূন্য থেকে | "কঠিন integration করেছেন?" |
| ৭ | সরকারি NID / KYC verification | third-party integration · ঢাকায় খুব কম লোক পেরেছে |
| ৮ | Hellotask-এ ৪ জনের team lead, deploy সহ | leadership |
| ৯ | Python + MQTT + WebSocket real-time service | "শুধু Laravel পারেন?" |
| ১০ | Dependency advisory-তে CI লাল, version তুলে সমাধান | security · CI/CD |
⚠ গল্প বলার দুইটা নিয়ম
শেষে জিজ্ঞেস করবে "আপনার কোনো প্রশ্ন আছে?" — "না" বললে নম্বর কাটা যায়। তিনটা তৈরি রাখো।