Laravel-ലെ $casts പ്രോപ്പർട്ടി, അധികം ശ്രദ്ധിക്കപ്പെടാത്ത എന്നാൽ ഡാറ്റ കൈകാര്യം ചെയ്യുന്ന രീതിയെ സ്വാധീനിക്കുന്ന ഒരു സൗകര്യമാണ്. boolean അല്ലെങ്കിൽ datetime പോലുള്ള ബിൽറ്റ്-ഇൻ ടൈപ്പുകൾ ഈ അറേയിൽ നൽകിയാൽ, Eloquent ഡാറ്റാബേസിൽ നിന്നുള്ള റോ (raw) സ്ട്രിംഗുകളെ നിങ്ങളുടെ കൺട്രോളറുകളിലേക്കോ വ്യൂകളിലേക്കോ എത്തുന്നതിന് മുമ്പ് തന്നെ കൂടുതൽ ഉപയോഗപ്രദമായ രീതിയിലേക്ക് മാറ്റുന്നു. ഇത് നിങ്ങളുടെ കോഡ് വൃത്തിയായി സൂക്ഷിക്കുന്നു. എന്നാൽ ആ അറേയ്ക്കുള്ളിൽ നിങ്ങളുടെ സ്വന്തം ഹെൽപ്പർ ഫംഗ്ഷൻ നേരിട്ട് വിളിക്കാൻ ശ്രമിച്ചാൽ ഫ്രെയിംവർക്ക് അത് അനുവദിക്കില്ല. 'username' => 'encrypt_data()' എന്ന് എഴുതുന്നത് പ്രവർത്തിക്കില്ല. ഒരു നേറ്റീവ് കാസ്റ്റ് കീവേഡോ അല്ലെങ്കിൽ CastsAttributes ഇന്റർഫേസ് ഇംപ്ലിമെന്റ് ചെയ്യുന്ന ഒരു ക്ലാസ്സോ ആണ് Laravel പ്രതീക്ഷിക്കുന്നത്. ഈ നിബന്ധന നിലനിൽക്കുന്നത് കാസ്റ്റിംഗ് ലെയർ Eloquent-ന്റെ ഹൈഡ്രേഷൻ (hydration), സീരിയലൈസേഷൻ (serialization) സൈക്കിളിന്റെ ആഴത്തിലുള്ള ഭാഗമായതുകൊണ്ടാണ്; റൺടൈമിൽ നിലവിലില്ലാത്ത ഒരു ഫംഗ്ഷൻ സ്ട്രിംഗിനേക്കാൾ, വ്യക്തമായ കരാറുള്ള (contract) ഒരു പ്രെഡിക്റ്റബിൾ ഒബ്ജക്റ്റാണ് ഇതിന് ആവശ്യം.
എന്തുകൊണ്ടാണ് $casts അറേയിൽ ഹെൽപ്പർ ഫംഗ്ഷനുകൾ പരാജയപ്പെടുന്നത്?
Eloquent ഒരു മോഡൽ ലോഡ് ചെയ്യുമ്പോൾ, ഓരോ കോളവും എങ്ങനെ മാറ്റണമെന്ന് തീരുമാനിക്കാൻ അത് $casts അറേ പരിശോധിക്കുന്നു. string, integer, array, encrypted തുടങ്ങിയ പ്രിമിറ്റീവ് ടൈപ്പുകളെയും CastsAttributes ഇംപ്ലിമെന്റ് ചെയ്യുന്ന ക്ലാസ് പേരുകളെയും ഫ്രെയിംവർക്ക് തിരിച്ചറിയുന്നു. ഇത് സ്ട്രിംഗുകളെ കോഡായി പരിഗണിക്കുന്നില്ല. അതിനാൽ 'encrypt_data()' എന്നത് ഒരു അജ്ഞാത കാസ്റ്റ് ടൈപ്പായി കണക്കാക്കപ്പെടുകയും അത് എറർ (error) ഉണ്ടാക്കുകയും ചെയ്യുന്നു. Laravel ഇത് പാഴ്സ് (parse) ചെയ്തിട്ടുണ്ടെങ്കിൽ പോലും, മോഡൽ ഇൻസ്റ്റൻസ്, അറ്റ്രിബ്യൂട്ട് കീ, നിലവിലെ മൂല്യം, മറ്റ് അറ്റ്രിബ്യൂട്ടുകൾ എന്നിവ ശരിയായ ക്രമത്തിൽ നിങ്ങളുടെ ഫംഗ്ഷനിലേക്ക് കൈമാറാൻ വിശ്വസനീയമായ ഒരു മാർഗ്ഗമുണ്ടാകില്ല. നിങ്ങളുടെ കസ്റ്റം ലോജിക്കും Eloquent-ന്റെ ഇന്റേണൽസും തമ്മിലുള്ള ആ കൈമാറ്റം (handshake) സ്റ്റാൻഡേർഡൈസ് ചെയ്യാനാണ് ഈ ഇന്റർഫേസ് നിലനിൽക്കുന്നത്.
ഈ പ്രശ്നം പരിഹരിക്കാൻ നിങ്ങൾക്ക് രണ്ട് മികച്ച വഴികളുണ്ട്. ഒന്ന് ദീർഘകാലാടിസ്ഥാനത്തിലുള്ള പുനരുപയോഗത്തിന് (reuse) അനുയോജ്യമാണ്. മറ്റൊന്ന് ഒരു മോഡലിനുള്ളിൽ മാത്രം വേഗത്തിൽ മാറ്റം വരുത്താൻ സഹായിക്കുന്നു.
രീതി 1: ഒരു കസ്റ്റം കാസ്റ്റ് ക്ലാസ് എഴുതുക
ഒരേ മാറ്റം തന്നെ ഒന്നിലധികം ഫീൽഡുകൾക്കോ അല്ലെങ്കിൽ പല മോഡലുകൾക്കോ ആവശ്യമുണ്ടെങ്കിൽ, ഒരു പ്രത്യേക കാസ്റ്റ് ക്ലാസ് ഉപയോഗിക്കുന്നതാണ് നല്ലത്. ഇത് സ്വന്തമായി ഒരു ഫയലിൽ നിലനിൽക്കുന്നു, ഐസൊലേഷനിൽ യൂണിറ്റ് ടെസ്റ്റ് ചെയ്യാം, കൂടാതെ നിങ്ങളുടെ മോഡലുകളിൽ ആവർത്തിച്ചുള്ള ബോയിലർപ്ലേറ്റ് (boilerplate) കോഡുകൾ ഒഴിവാക്കാം.
app/Casts/CustomEncrypt.php എന്ന ഫയൽ നിർമ്മിച്ചുകൊണ്ട് തുടങ്ങുക. നെയിംസ്പേസ് (namespace) നിങ്ങളുടെ ഓട്ടോലോഡിംഗ് സെറ്റപ്പിന് അനുസരിച്ചായിരിക്കണം, സാധാരണയായി 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);
}
}
മെത്തേഡ് സിഗ്നേച്ചറുകൾ (signatures) പ്രധാനമാണ്. Eloquent ഓരോ മെത്തേഡിലേക്കും നാല് ആർഗ്യുമെന്റുകൾ കൈമാറുന്നു. $model എന്നത് നിലവിൽ ഉപയോഗിക്കുന്ന അല്ലെങ്കിൽ സേവ് ചെയ്യുന്ന ഇൻസ്റ്റൻസ് ആണ്, ഇത് നിങ്ങളുടെ ലോജിക് മറ്റ് ഫീൽഡുകളെ ആശ്രയിച്ചാണെങ്കിൽ അവ പരിശോധിക്കാൻ സഹായിക്കുന്നു. $key എന്നത് നിലവിൽ കാസ്റ്റ് ചെയ്യപ്പെടുന്ന കോളത്തിന്റെ പേരാണ്. $value എന്നത് get ചെയ്യുമ്പോൾ ഡാറ്റാബേസിൽ നിന്നുള്ള റോ സ്ട്രിംഗോ അല്ലെങ്കിൽ set ചെയ്യുമ്പോൾ ഉപയോക്താവ് നൽകുന്ന മൂല്യമോ ആണ്. $attributes എന്നത് ആ വരിയിലെ എല്ലാ കോളങ്ങളുടെയും റോ (raw) അറേ ആണ്. നിങ്ങൾക്ക് എല്ലാ സമയത്തും നാല് ആർഗ്യുമെന്റുകളും ഉപയോഗിക്കേണ്ടതില്ല, എങ്കിലും ഇന്റർഫേസ് അവ ആവശ്യപ്പെടുന്നു.
മുകളിലെ get മെത്തേഡിൽ, Eloquent ഡാറ്റാബേസിൽ നിന്ന് വരി എടുക്കുന്നതിന് ശേഷവും മൂല്യം മോഡലിൽ എത്തുന്നതിന് മുമ്പും decrypt_data($value) പ്രവർത്തിക്കുന്നു. set മെത്തേഡിൽ, encrypt_data($value) പ്രവർത്തിക്കുന്നത് INSERT അല്ലെങ്കിൽ UPDATE ചെയ്യുന്നതിന് മുമ്പാണ്, ഇത് ഡാറ്റാബേസിൽ പ്ലെയിൻ ടെക്സ്റ്റ് (plaintext) പോകാതിരിക്കാൻ സഹായിക്കുന്നു.
ഇത് ഉപയോഗിക്കാൻ, നിങ്ങളുടെ മോഡലിലെ $casts അറേയിൽ ക്ലാസ് റഫറൻസ് ചെയ്യുക:
protected $casts = [
'username' => CustomEncrypt::class,
'password' => CustomEncrypt::class,
];
നിങ്ങൾ ക്ലാസ് കോൺസ്റ്റന്റ് (class constant) ഉപയോഗിക്കുന്നതിനാൽ, ബാക്കി കാര്യങ്ങൾ ഓട്ടോലോഡർ കൈകാര്യം ചെയ്യും. നിങ്ങളുടെ ആപ്ലിക്കേഷനിൽ പിന്നീട് എൻക്രിപ്ഷൻ രീതി മാറ്റണമെന്നുണ്ടെങ്കിൽ, ഒരു ഫയൽ മാത്രം എഡിറ്റ് ചെയ്താൽ മതി, എല്ലാ ഫീൽഡുകളുടെയും പെരുമാറ്റം ഉടൻ തന്നെ മാറും. പല ടേബിളുകളിലായി സെൻസിറ്റീവ് ഡാറ്റ കൈകാര്യം ചെയ്യുമ്പോൾ ഈ സെൻട്രലൈസേഷൻ വളരെ ഗുണകരമാണ്.
രീതി 2: അക്സസ്സറും (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 വായിക്കുമ്പോൾ, അത് റോ മൂല്യത്തെ get ക്ലോഷറിലൂടെ കടത്തിവിടുന്നു. നിങ്ങൾ മോഡലിന്റെ username പ്രോപ്പർട്ടിയിലേക്ക് പുതിയൊരു മൂല്യം നൽകുമ്പോൾ, Eloquent ക്വറി നിർമ്മിക്കുന്നതിന് മുമ്പ് set ക്ലോഷർ അത് എൻക്രിപ്റ്റ് ചെയ്യുന്നു.
പ്രോട്ടോടൈപ്പുകളിലോ (prototypes) അല്ലെങ്കിൽ ഒരുപാട് കാസ്റ്റ് ക്ലാസുകൾ ഉണ്ടാക്കുന്നത് അനാവശ്യമായി തോന്നുന്ന പഴയ മോഡലുകളിലോ (legacy models) ഈ രീതി മികച്ചതാണ്. ഇതിന്റെ പോരായ്മ ആവർത്തനമാണ് (repetition). പിന്നീട് email, phone, backup_code എന്നിവയ്ക്കും ഇതേ രീതി വേണമെന്ന് നിങ്ങൾ തീരുമാനിച്ചാൽ, ആ ക്ലോഷറുകൾ പല മെത്തേഡുകളിലായി കോപ്പി ചെയ്യേണ്ടി വരും. ഇത് കോഡിൽ അനാവശ്യമായ തിരക്ക് കൂട്ടുന്നു. കൂടാതെ, മുഴുവൻ മോഡലും ബൂട്ട് ചെയ്യാതെ തന്നെ യൂണിറ്റ് ടെസ്റ്റുകളിൽ ഈ ലോജിക് വീണ്ടും ഉപയോഗിക്കുന്നത് ഇത് തടയുന്നു.
ഇവ രണ്ടിലൊന്ന് തിരഞ്ഞെടുക്കുമ്പോൾ
പുനരുപയോഗം (reuse), ടെസ്റ്റിംഗ് (testing), മോഡലുകൾ ലളിതമായി (slim) നിലനിർത്തുക എന്നിവയ്ക്ക് പ്രാധാന്യം നൽകുമ്പോൾ ഒരു കസ്റ്റം കാസ്റ്റ് ക്ലാസ് (custom cast class) ഉപയോഗിക്കുക. ഈ മാറ്റം (transformation) നിങ്ങളുടെ ആപ്ലിക്കേഷനിലെ ഒരു പ്രധാനപ്പെട്ട സങ്കല്പമാണെന്നും വെറുമൊരു താൽക്കാലിക പരിഹാരമല്ലെന്നും (one-off hack) ഇത് മറ്റ് ഡെവലപ്പർമാർക്ക് സൂചിപ്പിക്കുന്നു.
ലോജിക് വളരെ പരിമിതമായ ഇടങ്ങളിൽ മാത്രം ആവശ്യമുള്ളതോ, പരീക്ഷണാടിസ്ഥാനത്തിലുള്ളതോ, അല്ലെങ്കിൽ നിലവിലെ സ്പ്രിന്റിന് (sprint) ശേഷം ആവശ്യമില്ലാതാകാൻ സാധ്യതയുള്ളതോ ആണെങ്കിൽ ഒരു ആക്സസ്സർ (accessor) ഉപയോഗിക്കുക. ഇത് കോഡ്ബേസിലുടനീളം ചെറിയ ഫയലുകൾ ചിതറിക്കിടക്കുന്നത് ഒഴിവാക്കി വേഗത്തിൽ മുന്നോട്ട് പോകാൻ സഹായിക്കുന്നു. എന്നാൽ ഇതേ രീതി രണ്ടാമതൊ മൂന്നാമതൊ തവണ ആവർത്തിക്കുമ്പോൾ, അത് ഒരു കാസ്റ്റ് ക്ലാസിലേക്ക് റീഫാക്ടർ (refactor) ചെയ്യാൻ തയ്യാറായിരിക്കുക.
ശ്രദ്ധിക്കേണ്ട പ്രായോഗിക കാര്യങ്ങൾ
കസ്റ്റം കാസ്റ്റുകൾ ശക്തമാണ്, എന്നാൽ നിങ്ങൾ ശ്രദ്ധിച്ചില്ലെങ്കിൽ അവ അപ്രതീക്ഷിതമായ പെരുമാറ്റങ്ങൾ (behavior) ഉണ്ടാക്കിയേക്കാം. ഒന്നാമതായി, ഡാറ്റാബേസ് നൽകുന്ന null ഉൾപ്പെടെയുള്ള ഏത് മൂല്യവും get ഫംഗ്ഷൻ സ്വീകരിക്കുന്നുണ്ടെന്ന് ഓർക്കുക. decrypt_data ഫംഗ്ഷൻ null ഇൻപുട്ട് സ്വീകരിക്കില്ലെങ്കിൽ, അതിനെ പ്രതിരോധിക്കാനുള്ള സംവിധാനം ഒരുക്കുക:
public function get(Model $model, string $key, mixed $value, array $attributes): mixed
{
return is_null($value) ? null : decrypt_data($value);
}
രണ്ടാമതായി, അറേ (array), JSON സീരിയലൈസേഷൻ (serialization) സമയത്താണ് കാസ്റ്റുകൾ പ്രവർത്തിക്കുന്നത്. നിങ്ങൾ ഒരു മോഡൽ ഒരു API റിസോഴ്സായി തിരികെ നൽകുകയോ toArray() വിളിക്കുകയോ ചെയ്യുമ്പോൾ, കാസ്റ്റ് get ലോജിക് ഇപ്പോഴും ബാധകമായിരിക്കും. സാധാരണയായി നിങ്ങൾ ആഗ്രഹിക്കുന്നത് ഇതാണ്, എങ്കിലും ഡീക്രിപ്റ്റ് ചെയ്ത മൂല്യങ്ങൾ (decrypted values) പുറത്തുവിടുന്നതിനൊപ്പം കൂടുതൽ വിസിബിലിറ്റി കൺട്രോളുകൾ (visibility controls) ആവശ്യമുണ്ടെങ്കിൽ ഇത് ഓർമ്മിക്കുന്നത് നന്നായിരിക്കും.
മൂന്നാമതായി, ഏറ്റവും പ്രധാനപ്പെട്ട കാര്യം, ഡാറ്റ പിരിച്ചെടുക്കുന്നതിന് ശേഷം ആണ് കാസ്റ്റിംഗ് നടക്കുന്നത്. മാറ്റം വരുത്തിയ മൂല്യത്തെ (transformed value) അടിസ്ഥാനമാക്കി നിങ്ങൾക്ക് ക്വറി (query) ചെയ്യാൻ കഴിയില്ല. User::where('username', 'john_doe')->first() എന്നതുപോലെയുള്ള ഒരു ക്വറി john_doe എന്ന സ്ട്രിംഗിനെ നേരിട്ട് ഡാറ്റാബേസിലേക്ക് അയക്കുന്നു. ഇത് ഒരിക്കലും നിങ്ങളുടെ ഡീക്രിപ്റ്റ് ലോജിക് വഴിയല്ല കടന്നുപോകുന്നത്. കോളത്തിലെ ഡാറ്റ എൻക്രിപ്റ്റ് ചെയ്ത് സൂക്ഷിച്ചിട്ടുണ്ടെങ്കിൽ, നിങ്ങൾ എൻക്രിപ്റ്റ് ചെയ്ത സൈഫർ ടെക്സ്റ്റിനെ (ciphertext) തന്നെ തിരയാതെ ആ ക്വറിക്ക് ഒന്നും കണ്ടെത്താൻ കഴിയില്ല. അതിനാൽ നിങ്ങളുടെ ഡാറ്റാബേസ് ആക്സസ് രീതികൾ (access patterns) അതനുസരിച്ച് പ്ലാൻ ചെയ്യുക; കാരണം കാസ്റ്റുകൾ നാറ്റീവ് ഡാറ്റാബേസ് ഫംഗ്ഷനുകൾക്കോ ഇൻഡക്സ് ചെയ്ത പ്ലെയിൻ ടെക്സ്റ്റ് കോളങ്ങൾക്കോ പകരമാവില്ല.
ചെറിയ കോൺഫിഗറേഷനുകൾക്കായി കസ്റ്റം കാസ്റ്റ് ക്ലാസുകൾ ഉപയോഗിക്കാം. നിങ്ങൾക്ക് കൺസ്ട്രക്റ്റർ ആർഗ്യുമെന്റുകൾ (constructor arguments) ആവശ്യമുണ്ടെങ്കിൽ—ഉദാഹരണത്തിന് ഒരു സൈഫർ മോഡോ (cipher mode) ഫോർമാറ്റ് സ്ട്രിംഗോ (format string) പാസ്സ് ചെയ്യണമെങ്കിൽ—Laravel അവയെ $casts അറേയിലൂടെ പിന്തുണയ്ക്കുന്നു. 'field' => CustomEncrypt::class . ':arg' എന്ന രീതിയിലുള്ള എക്സ്പ്രഷൻ ഇതിനായി ഉപയോഗിക്കാം, എങ്കിലും ഇത് ഇവിടെ വിവരിച്ച അടിസ്ഥാന സെറ്റപ്പിന് അപ്പുറമുള്ള കാര്യമാണ്.
യഥാർത്ഥ സാരം
$casts-ലേക്ക് നേരിട്ട് ഹെൽപ്പർ ഫംഗ്ഷനുകൾ ചേർക്കുന്നതിനെതിരെയുള്ള നിയന്ത്രണം വെറുതെ ഉണ്ടാക്കിയ നിയമങ്ങളല്ല. ഇത് വ്യക്തമായതും (explicit), ടെസ്റ്റ് ചെയ്യാൻ കഴിയുന്നതും (testable), പുനരുപയോഗിക്കാൻ കഴിയുന്നതും (reusable) ആയ കോഡ് എഴുതാൻ നിങ്ങളെ പ്രേരിപ്പിക്കുന്നു. കസ്റ്റം കാസ്റ്റ് ക്ലാസുകൾ ചിതറിക്കിടക്കുന്ന ഇൻലൈൻ ലോജിക്കുകളെ, ഡ്യൂപ്ലിക്കേഷൻ ഇല്ലാതെ വിവിധ മോഡലുകളിൽ പങ്കിടാൻ കഴിയുന്ന വിശ്വസനീയമായ ഘടകങ്ങളാക്കി മാറ്റുന്നു. ഒരു പ്രത്യേക ഫയൽ ഉണ്ടാക്കുന്നത് അനാവശ്യ സങ്കീർണ്ണതയായി തോന്നുമ്പോൾ, വേഗത്തിലുള്ളതും പരിമിതവുമായ പരിഹാരങ്ങൾക്കായി ആക്സസ്സറുകൾ (accessors) സഹായിക്കുന്നു. ഇവ രണ്ടും പഠിച്ചെടുക്കുക, പ്രശ്നത്തിന്റെ വ്യാപ്തി അനുസരിച്ച് ശരിയായത് തിരഞ്ഞെടുക്കുക; അങ്ങനെ നിങ്ങളുടെ ആപ്ലിക്കേഷൻ വളർന്നാലും Eloquent ലെയർ എളുപ്പത്തിൽ വായിക്കാവുന്ന രീതിയിൽ നിലനിൽക്കും.
