در آموزش آشنایی با متدهای به اصطلاح Magic در زبان PHP با مَجیک متدهای ()set__
و ()get__
آشنا شدیم؛ حال در این آموزش قصد داریم تا نگاهی دقیقتر به مفاهیم Setter و Getter در شیئگرایی بیندازیم. در واقع، در متودولوژی OOP استفاده از متدهای سِتِر و گِتِر یک پارادایم استاندارد به منظور دستیابی به پراپرتیهای کلاس است.
به طور کلی، میتوان گفت که متدهایی از جنس سِتِر به منظور اعتبارسنجی دیتا سپس سِت کردن آنها برای پراپرتیها به کار میروند و چنانچه در آینده بخواهیم قوانین جدیدی برای اعتبارسنجی وضع کنیم، صرفاً نیاز است تا آنها را داخل یک متد پیادهسازی کنیم. در چنین مواقعی، پراپرتی را از جنس private
قرار داده تا این تضمین ایجاد گردد آبجکتی که حاوی آن پراپرتی است پس از عبور از فیلتر متد سِتِر به آن پراپرتی دست یافته و از همین روی همان دیتایی را در دسترس دارد که مد نظرمان است. متدهای گِتِر نیز همانطور که از نامشان مشخص است، وظیفهٔ فراخوانی دیتا را با همان فرمتی دارند که مد نظر توسعهدهنده است.
در پاسخ به این پرسش که «استفاده از این دست متدها به جای دستیابی مستقیم به پراپرتیها چه فایدهای دارا است؟» میتوان گفت که اگر پراپرتیهای کلاس از جنس public
باشند، با توجه به این که چنین اِسکوپی سراسری است و پراپرتیها از دیگر جاها در دسترس میباشند، هیچ کنترلی روی این موضوع که چه مقداری برای آنها اختصاص خواهد یافت نداریم و هر فایل دیگری در پروژه به سادگی میتواند مقادیر آنها را تغییر دهد و همین مسئله سورسکد را آسیبپذیر میسازد اما چنانچه یک پراپرتی را از جنس private
تعریف کرده سپس یک متد سِتِر از جنس public
به منظور مقداردهی آن تعریف نماییم، به سادگی میتوان قوانینی داخل این متد تعریف کرد که پراپرتی مذکور چه نوع دیتایی را میتواند در خود ذخیره سازد مضاف بر این که علاوه بر Validation (چک کردن نوع دیتا) میتوان Sanitization (حذف دیتای مشکوک) را نیز داخل چنین متدهایی اِعمال نمود. در یک کلام، میتوان فیلتری مابین پراپرتی مذکور و کدهای خارج از کلاس که قصد استفاده از آن را دارند ایجاد کرد.
حال به منظور درک بهتر مباحث تئوریک فوق، داخل پوشهٔ oop
پروژهای با نامی دلخواه همچون setter-and-getter
ساخته و همچون آموزشهای گذشته، داخل پوشهٔ classes
فایلی تحت عنوان User.php
ساخته و آن را به صورت زیر تکمیل میکنیم:
<?php
namespace SokanAcademy;
class User
{
public $name = 'Behzad';
public function getName()
{
return $this->name;
}
}
همانطور که میبینیم، یک پراپرتی از جنس public
تحت عنوان name$
ایجاد کرده که به عبارتی دارای یک اِسکوپ سراسری است و سطح دسترسی به آن بدون محدودیت است. سپس متدی تحت عنوان ()getName
ساختهایم که این وظیفه را دارا است تا مقدار این پراپرتی را ریترن کند. جهت تست، وارد فایل index.php
شده و از روی این کلاس یک آبجکت میسازیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
$user->name ='New Name';
echo $user->getName();
میبینیم که به سادگی قادریم به پراپرتی name$
از داخل فایلهای دیگر دسترسی یافته و مقدار آن را دستخوش تغییر سازیم سپس متد ()getName
را فراخوانی کنیم به طوری که از این پس مقدار پیشفرض این پراپرتی به استرینگی جدید تغییر یافته است. در ادامه، به عنوان تستی دیگر فایل فوق را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
$user->name ='<script>alert("You`re hacked!")</script>';
echo $user->getName();
همانطور که میبینیم، به عنوان مقدار پراپرتی name$
از یک کد سادهٔ جاوااسکریپتی استفاده کردهایم و در صورتی که این فایل را داخل مرورگر اجرا کنیم، با یک پاپآپ مواجه خواهیم شد و این بدان معنا است که کد ما ایمن نیست! برای رفع این مشکل، میتوانیم از پارادایمهای Setter و Getter که پیش از این با مفاهیم آنها آشنا شدیم بهره بگیریم به طوری که میتوان کلاس User
را به صورت زیر تغییر داد:
<?php
namespace SokanAcademy;
class User
{
private $name = 'Behzad';
public function getName()
{
return $this->name;
}
}
اولین کاری که انجام دادهایم آن است که سطح دسترسی پراپرتی name$
را به private
تغییر دادهایم و همانطور که در آموزش آشنایی با مفهوم Access Modifier در زبان PHP توضیح دادیم، سطح دسترسی private
این تضمین را ایجاد میکند که ما فقط و فقط از داخل کلاس User
به این پراپرتی دسترسی خواهیم داشت به طوری که اگر فایل index.php
را مجدد اجرا کنیم، با ارور زیر مواجه خواهیم شد:
/var/www/oop/setter-and-getter$ php index.php
PHP Fatal error: Uncaught Error: Cannot access private property SokanAcademy\User::$name in /var/www/oop/setter-and-getter/index.php:6
Stack trace:
#0 {main}
thrown in /var/www/oop/setter-and-getter/index.php on line 6
متن ارور حاکی از آن است که ما اجازهٔ دسترسی به یک پراپرتی از جنس private
را نداریم. حال کلاس User
را مجدد به صورت زیر تکمیلتر میکنیم:
<?php
namespace SokanAcademy;
class User
{
private $name;
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
در واقع، ابتدا به ساکن مقدار پیشفرض پراپرتی name$
را حذف کرده چرا که قصد داریم تا توسعهدهنده برای هر آبجکتی که ایجاد میکند آن را اختصاصاً مقداردهی نماید و در ادامه متدی از جنس public
که قابلدسترسی از سایر بخشهای سورسکد است به نام ()setName
ساختهایم که یک پارامتر ورودی تحت عنوان name$
میگیرد سپس آن را به پراپرتی نوشتهشده داخل بدنهٔ کلاس منتسب میکند و در ادامه متد دیگری به نام ()getName
نوشتهایم که این وظیفه را دارا است تا صرفاً مقدار پراپرتی name$
را ریترن کند. حال مجدد به فایل index.php
بازگشته و آن را به صورت زیر آپدیت میکنیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
$user->setName("Behzad");
echo $user->getName();
همانطور که مشاهده میشود، ابتدا متد ()setName
را روی آبجکت user$
فراخوانی کرده و آرگومانی از جنس استرینگ برای آن در نظر گرفتهایم سپس با استفاده از دستور echo
مقداری که متد ()getName
ریترن میکند را چاپ کردهایم و اگر این فایل را اجرا کنیم، میبینیم که مقدار در نظر گرفته شده برای پراپرتی name$
به درستی نمایش داده میشود.
پیش از این گفتیم که یکی از مزایای استفاده از متدهایی از جنس سِتِر آن است که میتوانیم اقدام به اعتبارسنجی دیتا کنیم. برای مثال، نوع فراخوانی متد ()setName
را در فایل index.php
را به صورت زیر تغییر میدهیم:
$user->setName("Long Stringgggggggggggggggggggggggggggggggggggggggggggggggg");
در حقیقت، به احتمال زیاد استرینگی به این شکل طولانی دیتایی نخواهد بود که دوست داشته باشیم وارد سیستم شود و اینجا است که به سادگی میتوانیم یک سری کد اعتبارسنجی برای متد ()setName
به صورت زیر در نظر بگیریم:
<?php
namespace SokanAcademy;
class User
{
private $name;
public function setName($name)
{
$msg = '';
if (strlen($name) < 3) {
$msg = 'Invalid Input';
} else if (strlen($name) > 20) {
$msg = 'Invalid Input';
} else {
$this->name = $name;
}
echo $msg;
}
public function getName()
{
return $this->name;
}
}
همانطور که ملاحظه میشود، صرفاً کدهای داخل متد ()setName
را تغییر دادهایم بدین صورت که ابتدا به ساکن یک متغیر تحت عنوان msg$
با مقدار پیشفرضی همچون یک استرینگ خالی ساخته سپس با استفاده از دستورات شرطی چک کردهایم ببینیم که آیا طول رشتهٔ پارامتر ورودی name$
از ۳ کاراکتر کوچکتر است یا خیر که اگر این گونه بود، وارد دستور if
شده و مقدار «Invalid Input» را برای متغیر msg$
در نظر میگیریم؛ مجدد با استفاده از دستور else if
چک میکنیم ببینیم که آیا طول رشتهٔ مذکور بیش از ۲۰ کاراکتر است یا خیر که اگر این گونه بود، همان مقدار قبلی را برای متغیر msg$
در نظر گرفته و در نهایت اگر هیچ کدام از این دو شرط برآورده نشد، وارد دستور else
شده و پارامتر ورودی name$
را به پراپرتی name$
منتسب میکنیم و در نهایت هم متغیر msg$
را چاپ میکنیم. حال مجدد به فایل index.php
باز میگردیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
$user->setName("Long Stringgggggggggggggggggggggggggggggggggggggggggggggggg");
echo $user->getName();
در صورت اجرای این فایل خواهیم داشت:
Invalid Input
با توجه به این که طول رشته بیش از ۲۰ کاراکتر است، در متد ()setName
وارد بلوک else if
خواهیم شد. حال مجدد کدهای فوق را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
$user->setName("Hi");
echo $user->getName();
میبینیم با توجه به این که طول رشته کمتر از ۳ کاراکتر است، وارد بولک if
شده و بالتبع مقدار مد نظر به پراپرتی name$
منتسب نخواهد شد. برای بار دیگر، کدهای فوق را به صورت زیر آپدیت میکنیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
$user->setName(123456);
echo $user->getName();
در واقع، به عنوان آرگومان متد ()setName
یک عدد در نظر گرفتهایم که طول آن هم مجاز است به طوری که در خروجی خواهیم داشت:
123456
گرچه این کد کار میکند، اما از لحاظ منطقی درست نیست و قوانین اعتبارسنجی خود را باید به گونهای تغییر دهیم که دولوپر فقط و فقط مجاز به وارد کردن استرینگ باشد به طوری که نسخهٔ آپدیتشدهٔ متد ()setName
به صورت زیر خواهد بود:
public function setName($name)
{
$msg = '';
if (!is_string($name)) {
$msg = 'Invalid Input';
} else {
if (strlen($name) < 3) {
$msg = 'Invalid Input';
} else if (strlen($name) > 20) {
$msg = 'Invalid Input';
} else {
$this->name = $name;
}
}
echo $msg;
}
همانطور که ملاحظه میشود، یک دستور شرطی دیگر اضافه شده که داخل آن چک کردهایم ببینیم که آیا مقدار پارامتر ورودی name$
از جنس استرینگ است یا خیر که این کار را با استفاده از متد ()is_string
انجام دادهایم؛ اگر این گونه نبود، صرفاً متغیر msg$
را مقداردهی میکنیم و در غیر این صورت وارد بلوک else
شد و همان دستورات شرطی سابق را اجرا میکنیم. حال اگر مجدد فایل index.php
را اجرا کنیم، در خروجی خواهیم داشت:
Invalid Input
میبینیم با توجه به این که مقدار ورودی برای متد ()setName
یک عدد صحیح است، بالتبع وارد اولین دستور شرطی شدهایم اما اگر فایل فوق را به صورت زیر تغییر دهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
$user->setName("behzad_1362");
echo $user->getName();
در خروجی خواهیم داشت:
behzad_1362
با این توضیحات، میتوان نکتهٔ مهمی را متذکر شد و آن هم چیزی نیست جز این که استفاده از متدهای سِتِر این امکان را در اختیارمان میگذارند تا علاوه بر سِت کردن مقادیر پراپرتیهای private
و اطمینان حاصل کردن از این که دیتای آنها به سادگی دستخوش تغییر نخواهد شد، این امکان را در اختیار توسعهدهندگان میگذارند تا دیتای ورودی را اعتبارسنجی کنند مضاف بر این که هر موقع بخواهیم در قوانین اعتبارسنجی تغییری ایجاد کنیم، صرفاً نیاز است تا این تغییرات را فقط داخل یک متد اِعمال نماییم.
نکتهٔ دگیری که در ارتباط با متدهای Setter وجود دارد این است که ما میتوانیم به صورت دینامیک (پویا) نیز اقدام به سِت کردن پراپرتیهای مد نظر خود کنیم که در ادامه این موضوع را تشریح خواهیم کرد.
آشنایی با نحوهٔ سِت کردن پراپرتیها به صورت پویا
پیش از این دیدیم که با استفاده از مَجیک متد ()set__
به چه شکل میتوانستیم پراپرتیهایی که وجود خارجی نداشتند را سِت کرده سپس مورد استفاده قرار دهیم. حال در ادامه خواهیم دید که به چه روشی میتوان این دست پراپرتیها که اصطلاحاً دینامیک (پویا) هستند را با متدِ اختصاصی خودمان سِت کرده و مقداردهی کنیم. برای این منظور، متد جدیدی تحت عنوان ()setProperty
داخل کلاس User
ایجاد میکنیم:
<?php
namespace SokanAcademy;
class User
{
private $name;
public function setProperty($name, $val)
{
$this->{$name} = $val;
}
public function setName($name)
{
$msg = '';
if (!is_string($name)) {
$msg = 'Invalid Input';
} else {
if (strlen($name) < 3) {
$msg = 'Invalid Input';
} else if (strlen($name) > 20) {
$msg = 'Invalid Input';
} else {
$this->name = $name;
}
}
echo $msg;
}
public function getName()
{
return $this->name;
}
}
همانطور که ملاحظه میشود، این متد دارای دو پارامتر ورودی تحت عناوین name$
و val$
است که هر دو پارامتر نیز الزامی میباشند به طوری که پارامتر اول حاوی نام پراپرتیای است که قصد داریم به صورت دینامیک ایجاد کنیم و پارامتر دوم نیز مقدار آن است. داخل بدنهٔ این متد با استفاده از دستور this$
که پیش از این هم گفتیم به کلاس User
باز میگردد، گفتهایم که پارامتر ورودی name$
در بدنهٔ کلاس سِت شود اما همانطور که میبینیم، از علائم {}
در اطراف این پارامتر استفاده کردهایم که درج این علائم الزامی است سپس یک علامت =
قرار داده و در نهایت متغیر val$
را نوشتهایم. حال به منظور تست عملکرد این متد جدید، وارد فایل index.php
شده و آن را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
$user = new SokanAcademy\User();
$user->setName("Behzad");
echo $user->getName();
echo "\n";
$user->setProperty('age', 30);
echo $user->age;
در صورت اجرای این فایل در خروجی خواهیم داشت:
Behzad
30
در خط نهم متد ()setProperty
را فراخوانی کرده و به عنوان نام پراپرتی از age
و به عنوان مقدارش از عدد ۳۰ استفاده کردهایم و در ادامه نیز از طریق دستور user->age$
به این پراپرتی دسترسی یافته و مقدارش را چاپ کردهایم.
جمعبندی
استفاده از متدهای به اصطلاح Setter و Getter در متودولوژی OOP طرفداران خاص خود را دارا است و به همین دلیل در این آموزش به بررسی این مفاهیم پرداخته سپس در قالب مثالهایی کاربردی به نحوهٔ سِت کردن و گِت کردن پراپرتیهایی که از جنس private
هستند پرداختیم. همچنین در انتهای این آموزش دیدیم که به چه شکل میتوان به شکلی پویا اقدام به تعریف یک سری پراپرتی کرد.