Laravel-এর $casts প্রপার্টি হলো সেই সব নিরব সুবিধাসমূহের একটি যা খুব বেশি মনোযোগ আকর্ষণ না করেই আপনার ডেটা হ্যান্ডলিংয়ের ধরন বদলে দেয়। অ্যারেতে boolean বা datetime-এর মতো বিল্ট-ইন টাইপ যুক্ত করলেই Eloquent স্বয়ংক্রিয়ভাবে ডাটাবেসের র (raw) স্ট্রিংগুলোকে আপনার কন্ট্রোলার বা ভিউতে পৌঁছানোর আগেই আরও ব্যবহারযোগ্য কিছুতে রূপান্তর করে ফেলে। এটি আপনার কোডকে পরিচ্ছন্ন রাখে। কিন্তু আপনি যখনই সেই অ্যারের ভেতরে সরাসরি নিজের কোনো হেল্পার ফাংশন কল করার চেষ্টা করবেন, ফ্রেমওয়ার্কটি বাধা দেবে। 'username' => 'encrypt_data()' এর মতো কিছু লিখলে তা কাজ করবে না। Laravel হয় একটি নেটিভ কাস্ট কিওয়ার্ড অথবা CastsAttributes ইন্টারফেস ইমপ্লিমেন্ট করে এমন একটি ক্লাস আশা করে। এই প্রয়োজনীয়তাটি থাকার কারণ হলো কাস্টিং লেয়ারটি Eloquent-এর হাইড্রেশন (hydration) এবং সিরিয়ালাইজেশন (serialization) সাইকেলের গভীরে কাজ করে; রানটাইমে অস্তিত্বহীন হতে পারে এমন কোনো এলোমেলো ফাংশন স্ট্রিংয়ের পরিবর্তে এটি একটি নির্দিষ্ট কন্ট্রাক্টসহ একটি প্রেডিক্টেবল (predictable) অবজেক্ট চায়।
কেন $casts অ্যারেতে হেল্পার ফাংশন কাজ করে না
যখন Eloquent একটি মডেল লোড করে, তখন প্রতিটি কলাম কীভাবে ট্রান্সফর্ম করতে হবে তা নির্ধারণ করতে এটি $casts অ্যারেটি স্ক্যান করে। ফ্রেমওয়ার্কটি নির্দিষ্ট প্রিমিটিভ টাইপ—যেমন string, integer, array, encrypted ইত্যাদি—এবং CastsAttributes ইমপ্লিমেন্ট করে এমন ফুল্লি-কোয়ালিফাইড (fully-qualified) ক্লাস নেমগুলো চিনতে পারে। এটি স্ট্রিংটিকে কোড হিসেবে মূল্যায়ন করে না। তাই 'encrypt_data()'-কে একটি অজানা কাস্ট টাইপ হিসেবে গণ্য করা হয়, যা এরর (error) তৈরি করে। এমনকি Laravel যদি এটি পার্স (parse) করত, তবুও সঠিক ক্রমে মডেল ইনস্ট্যান্স, অ্যাট্রিবিউট কী, বর্তমান ভ্যালু এবং আশেপাশের অ্যাট্রিবিউটগুলো আপনার ফাংশনে পাস করার কোনো নির্ভরযোগ্য উপায় থাকত না। আপনার কাস্টম লজিক এবং Eloquent-এর ইন্টারনাল মেকানিজমের মধ্যে সেই হ্যান্ডশেক বা সমন্বয়কে স্ট্যান্ডার্ডাইজ করার জন্যই এই ইন্টারফেসটি রয়েছে।
এই ব্যবধান দূর করার জন্য আপনার কাছে দুটি কার্যকর উপায় রয়েছে। একটি দীর্ঘমেয়াদী পুনঃব্যবহারের জন্য উপযোগী, আর অন্যটি দ্রুত সমাধানের জন্য যখন আপনার কেবল একটি মডেলের ভেতরেই একটি ছোট পরিবর্তন প্রয়োজন।
পদ্ধতি ১: একটি কাস্টম কাস্ট ক্লাস লিখুন
যদি একই ট্রান্সফরমেশন একাধিক ফিল্ডে প্রযোজ্য হয় বা বেশ কয়েকটি মডেলে ছড়িয়ে থাকে, তবে একটি ডেডিকেটেড কাস্ট ক্লাস ব্যবহার করা সবচেয়ে ভালো সিদ্ধান্ত। এটি নিজস্ব ফাইলে থাকে, আলাদাভাবে ইউনিট-টেস্ট করা যায় এবং আপনার মডেলগুলোকে বারবার একই ধরনের কোড (boilerplate) লেখা থেকে মুক্ত রাখে।
প্রথমে app/Casts/CustomEncrypt.php ফাইলটি তৈরি করুন। নেমস্পেসটি আপনার অটোলোডিং সেটআপের সাথে মিল থাকা উচিত, সাধারণত এটি App\Casts হয়। ক্লাসটিকে অবশ্যই Illuminate\Contracts\Database\Eloquent\CastsAttributes ইমপ্লিমেন্ট করতে হবে, যা আপনাকে get এবং set নামে দুটি মেথড সংজ্ঞায়িত করতে বাধ্য করবে।
<?php
namespace App\Casts;
use Illuminate\Contracts\Database\Eloquent\CastsAttributes;
use Illuminate\Database\Eloquent\Model;
class CustomEncrypt implements CastsAttributes
{
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
return decrypt_data($value);
}
public function set(Model $model, string $key, mixed $value, array $attributes): mixed
{
return encrypt_data($value);
}
}
মেথডের সিগনেচারগুলো গুরুত্বপূর্ণ। Eloquent প্রতিটি মেথডে চারটি আর্গুমেন্ট পাস করে। $model হলো সেই ইনস্ট্যান্স যা পপুলেট বা সেভ করা হচ্ছে, যা আপনাকে অন্যান্য ফিল্ড পরীক্ষা করার সুযোগ দেয় যদি আপনার লজিক সেগুলোর ওপর নির্ভর করে। $key হলো বর্তমানে যে কলামটি কাস্ট করা হচ্ছে তার নাম। $value হলো get মেথডের ক্ষেত্রে ডাটাবেস থেকে আসা র (raw) স্ট্রিং বা null, অথবা set মেথডের ক্ষেত্রে ব্যবহারকারীর দেওয়া ভ্যালু। $attributes হলো ওই রো (row)-এর সমস্ত কলামের একটি র (raw) অ্যারে। আপনাকে প্রতিবার চারটি আর্গুমেন্টই ব্যবহার করতে হবে এমন নয়, তবে ইন্টারফেসের নিয়ম অনুযায়ী এগুলো থাকা প্রয়োজন।
উপরের get মেথডে, Eloquent ডাটাবেস থেকে রোটি নিয়ে আসার পর এবং ভ্যালুটি আপনার মডেলে পৌঁছানোর আগে decrypt_data($value) রান করে। set মেথডে, INSERT বা UPDATE করার আগে encrypt_data($value) রান করে, যা নিশ্চিত করে যে ডাটাবেস কখনোই প্লেইন টেক্সট (plaintext) দেখবে না।
এটি সেটআপ করতে আপনার মডেলের $casts অ্যারের ভেতরে ক্লাসটিকে রেফারেন্স হিসেবে ব্যবহার করুন:
protected $casts = [
'username' => CustomEncrypt::class,
'password' => CustomEncrypt::class,
];
যেহেতু আপনি ক্লাস কনস্ট্যান্ট ব্যবহার করছেন, তাই অটোলোডার বাকি কাজগুলো সামলে নেবে। পরবর্তীতে যদি আপনার অ্যাপ্লিকেশনে এনক্রিপশন স্কিম পরিবর্তন করার প্রয়োজন হয়, তবে আপনাকে কেবল একটি ফাইল এডিট করতে হবে এবং প্রতিটি ম্যাপ করা ফিল্ডের আচরণ তাৎক্ষণিকভাবে পরিবর্তিত হয়ে যাবে। অনেকগুলো টেবিলের মধ্যে সেনসিটিভ ডেটা ম্যানেজ করার ক্ষেত্রে এই সেন্ট্রালাইজেশন বা কেন্দ্রীয়করণ অতুলনীয়।
পদ্ধতি ২: অ্যাক্সেসর (Accessor) এবং মিউটেটর (Mutator) ব্যবহার করুন
মাঝে মাঝে এমন কোনো ট্রান্সফরমেশনের জন্য আপনি নতুন ফাইল তৈরি করতে চান না যা কেবল একটি নির্দিষ্ট স্থানে প্রয়োজন। Laravel-এর Attribute ক্লাস আপনাকে PHP 8+ ক্লোজার (closure) সিনট্যাক্স ব্যবহার করে সরাসরি মডেলে get এবং set লজিক সংজ্ঞায়িত করার সুযোগ দেয়।
use Illuminate\Database\Eloquent\Casts\Attribute;
protected function username(): Attribute
{
return Attribute::make(
get: fn ($value) => decrypt_data($value),
set: fn ($value) => encrypt_data($value),
);
}
এখানে মেথডের নামটি অবশ্যই আপনার টার্গেট করা কলাম বা অ্যাট্রিবিউটের সাথে মিল থাকতে হবে। যখন Eloquent ডাটাবেস থেকে username রিড করে, তখন এটি র (raw) ভ্যালুটিকে get ক্লোজারের মাধ্যমে পাস করে। যখন আপনি মডেলের username প্রপার্টিতে নতুন কোনো ভ্যালু অ্যাসাইন করেন, তখন Eloquent কুয়েরি তৈরি করার আগেই set ক্লোজারটি সেটিকে এনক্রিপ্ট করে ফেলে।
প্রোটোটাইপ বা লিগ্যাসি (legacy) মডেলের ক্ষেত্রে এই পদ্ধতিটি বেশ কার্যকর, যেখানে আলাদা আলাদা কাস্ট ক্লাসের ডিরেক্টরি তৈরি করা অনেকটা অতিরিক্ত মনে হতে পারে। এর অসুবিধা হলো কোড রিপিটেশন বা পুনরাবৃত্তি। আপনি যদি পরবর্তীতে সিদ্ধান্ত নেন যে email, phone, এবং backup_code-এর জন্যও একই ব্যবস্থা প্রয়োজন, তবে আপনাকে বারবার সেই ক্লোজারগুলো বিভিন্ন মেথড বা মডেলে কপি করতে হবে। এই বাড়তি কোড বা নয়েজ (noise) সময়ের সাথে বাড়তে থাকে। এছাড়া এটি পুরো মডেলটি বুট (boot) না করেই ইউনিট টেস্টে লজিকটি পুনরায় ব্যবহার করাকেও বাধাগ্রস্ত করে।
দুটির মধ্যে কোনটি বেছে নেবেন
যখন আপনি কোড পুনরায় ব্যবহার (reuse), টেস্টিং এবং মডেলগুলোকে হালকা (slim) রাখার বিষয়ে গুরুত্ব দেন, তখন একটি কাস্টম কাস্ট ক্লাস (custom cast class) ব্যবহার করুন। এটি অন্যান্য ডেভেলপারদের সংকেত দেয় যে এই রূপান্তরটি আপনার অ্যাপ্লিকেশনের একটি গুরুত্বপূর্ণ অংশ, কোনো সাময়িক বা অস্থায়ী সমাধান (one-off hack) নয়।
যখন লজিকটি সম্পূর্ণভাবে স্থানীয় (localized), পরীক্ষামূলক, অথবা বর্তমান স্প্রিন্টের (sprint) পরে আর প্রয়োজন হবে না বলে মনে হয়, তখন একটি অ্যাক্সেসর (accessor) ব্যবহার করুন। এটি কোডবেসের বিভিন্ন জায়গায় ছোট ছোট ফাইল ছড়িয়ে না দিয়ে দ্রুত কাজ করতে সাহায্য করে। তবে, যখন একই প্যাটার্ন দ্বিতীয় বা তৃতীয়বার দেখা দেবে, তখন সেটিকে একটি কাস্ট ক্লাস হিসেবে রিফ্যাক্টর (refactor) করার জন্য প্রস্তুত থাকুন।
মনে রাখার মতো কিছু ব্যবহারিক বিষয়
কাস্টম কাস্টগুলো শক্তিশালী, কিন্তু আপনি যদি সতর্ক না থাকেন তবে এগুলো এমন আচরণ করতে পারে যা আপনাকে অবাক করে দিতে পারে। প্রথমত, মনে রাখবেন যে get মেথড ডাটাবেস থেকে প্রাপ্ত যেকোনো কিছু গ্রহণ করে, যার মধ্যে null-ও অন্তর্ভুক্ত। যদি decrypt_data ফাংশনটি null ইনপুট গ্রহণ করতে না পারে, তবে এর বিরুদ্ধে সুরক্ষা ব্যবস্থা (guard) রাখুন:
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
return is_null($value) ? null : decrypt_data($value);
}
দ্বিতীয়ত, অ্যারে (array) এবং JSON সিরিয়ালাইজেশনের (serialization) সময় কাস্টগুলো কার্যকর হয়। যখন আপনি একটি মডেলকে API resource হিসেবে রিটার্ন করেন বা toArray() কল করেন, তখন কাস্টের get লজিকটি তখনও কাজ করে। সাধারণত এটিই আপনি চান, তবে আপনি যদি ডিক্রিপ্ট করা (decrypted) ভ্যালুগুলো প্রকাশ করেন এবং তার ওপর অতিরিক্ত ভিজিবিলিটি কন্ট্রোল (visibility controls) যোগ করতে চান, তবে এটি মনে রাখা জরুরি।
তৃতীয়ত, এবং সবচেয়ে গুরুত্বপূর্ণ বিষয় হলো, কাস্টিং ঘটে ডেটা সংগ্রহের পর। আপনি রূপান্তরিত (transformed) ভ্যালুর ওপর ভিত্তি করে কুয়েরি (query) করতে পারবেন না। User::where('username', 'john_doe')->first() এর মতো একটি কুয়েরি সরাসরি ডাটাবেসে john_doe স্ট্রিংটি পাঠিয়ে দেয়। এটি কখনোই আপনার ডিক্রিপ্ট লজিকের মধ্য দিয়ে যায় না। যদি কলামটি এনক্রিপ্টেড অবস্থায় থাকে, তবে আপনি যদি সরাসরি এনক্রিপ্টেড সাইফারটেক্সট (ciphertext)-এর বিরুদ্ধে অনুসন্ধান না করেন, তবে সেই কুয়েরি কোনো ফলাফল খুঁজে পাবে না। আপনার ডাটাবেস অ্যাক্সেস প্যাটার্নগুলো সেই অনুযায়ী পরিকল্পনা করুন, কারণ কাস্টগুলো নেটিভ ডাটাবেস ফাংশন বা ইনডেক্সড প্লেইনটেক্সট (plaintext) কলামের বিকল্প নয়।
কাস্টম কাস্ট ক্লাসগুলো ছোটখাটো কনফিগারেশনের জন্যও চমৎকার জায়গা হতে পারে। যদি আপনার কনস্ট্রাক্টর আর্গুমেন্ট (constructor arguments) প্রয়োজন হয়—যেমন কোনো সাইফার মোড (cipher mode) বা ফরম্যাট স্ট্রিং পাস করা—তবে Laravel $casts অ্যারের মাধ্যমে 'field' => CustomEncrypt::class . ':arg' এর মতো এক্সপ্রেশন ব্যবহার করে এগুলো সাপোর্ট করে, যদিও এটি এখানে বর্ণিত প্রাথমিক সেটআপের চেয়ে কিছুটা উন্নত পর্যায়ের।
মূল শিক্ষা
সরাসরি $casts-এ হেল্পার ফাংশন ব্যবহার না করার সীমাবদ্ধতাটি কোনো অযৌক্তিক নিয়ম নয়। এটি আপনাকে এমন কোড লিখতে উৎসাহিত করে যা স্পষ্ট (explicit), টেস্টযোগ্য (testable) এবং পুনরায় ব্যবহারযোগ্য (reusable)। কাস্টম কাস্ট ক্লাসগুলো ছড়িয়ে ছিটিয়ে থাকা ইনলাইন লজিককে নির্ভরযোগ্য কম্পোনেন্টে রূপান্তরিত করে যা আপনি ডুপ্লিকেশন ছাড়াই বিভিন্ন মডেলে শেয়ার করতে পারেন। অন্যদিকে, যখন একটি আলাদা ফাইল তৈরি করা অতিরিক্ত আনুষ্ঠানিকতা (ceremony) মনে হয়, তখন অ্যাক্সেসরগুলো দ্রুত এবং স্থানীয় সমাধানের পথ উন্মুক্ত রাখে। উভয় পদ্ধতিই আয়ত্ত করুন, সমস্যার পরিধি অনুযায়ী সঠিকটি বেছে নিন, তাহলে অ্যাপ্লিকেশন বড় হওয়ার পরেও আপনার Eloquent লেয়ারটি পড়ার যোগ্য (readable) থাকবে।
