آشنایی با تفاوت Pass by Value و Pass By Reference در زبان PHP

آشنایی با تفاوت Pass by Value و Pass By Reference در زبان PHP

گاهی‌اوقات در کدنویسی ما نیاز داریم تا به جای ساخت یک متغیر، آرایه، آبجکت یا هر چیز جدیدی، از الگوریتمی استفاده کنیم که تغییرات مد نظر را روی همان متغیر، آرایه یا آبجکت اعمال شده و به جای اشغال کردن فضایی جدید با ساخت کپی‌هایی از روی متغیرها، آرایه‌ها و یا آبجکت‌ها، همان داده‌های قبلی را دستخوش تغییر سازیم.

اینجا است که می‌بایست با مفهومی تحت عنوان Pass by Reference آشنا شویم. به طور کلی، از جمله مزایای چنین کاری می‌توان به این نکته اشاره کرد که به سادگی قادر خواهیم بود تا داده‌ها را بسته به نیاز خود تغییر داده تا در نهایت به متغیر، آرایه یا آبجکتی با تمامی خصوصیات مد نظرمان دست یابیم اما در عین حال بایستی توجه داشته باشیم که استفاده از چنین الگوریتمی می‌تواند یکسری اثرات جانبی نیز داشته باشد (مثلاً زمانی که آرایه یا آبجکتی که روی آن کار می‌کنیم، در خارج از فانکشن مد نظر ما هم استفاده شده باشد که در چنین شرایطی ممکن است با مشکل مواجه شویم چرا که ساختار آرایه یا آبجکت تغییر کرده است). پیش از هر چیز، بایستی ببینیم که منظور از Reference چیست:

یک Reference صرفاً راهی به منظور ارجاع دادن به محتویات یک Variable (متغیر) با استفاده نامی متفاوت است. به عبارت دیگر، رفرنس‌ها در زبان PHP همچون Shortcut در سیستم‌عامل ویندوز یا Symbolic Link در گنو/لینوکس یا Alias در مکینتاش هستند که برای دستیابی سریع استفاده می‌شوند.

آشنایی با مفهوم Pass by Value
بر اساس مستندات زبان PHP، به صورت پیش‌فرض پارامترهای ورودی فانکشن‌ها اگر متغیر یا آرایه باشند، اصطلاحاً Pass by Value هستند؛ به عبارت دیگر، چنانچه داخل فانکشن مد نظر مقادیر پارامترهای ورودی دستخوش تغییر گردند، هرگز این تغییر مقادیر روی پارامترها خارج از آن فانکشن تأثیری نخواهند گذاشت. به طور مثال داریم:

$valOne = 2;
$valTwo = 7;

function passByValue($paramOne, $paramTwo)
{
    $paramOne++;
    $paramTwo++;
    echo "paramOne + 1 multiplies paramTwo + 1 equals " . $paramOne * $paramTwo . "
"; } echo "Before calling the function, valOne equals $valOne & valTwo equals $valTwo" . "
"; passByValue($valOne, $valTwo); echo "After calling the function, valOne equals $valOne & valTwo equals $valTwo" . "
";

در همان‌طور که در بالا مشاهده می‌شود، دو متغیر تحت عناوین valOne$ و valTwo$ به ترتیب با مقادیر ۲ و ۷ ایجاد کرده‌ایم؛ سپس فانکشنی تحت عنوان ()passByValue نوشته‌ایم که دو پارامتر ورودی می‌گیرد تحت عناوین paramOne$ و paramTwo$. داخل این فانکشن ابتدا با استفاده از اپراتور ++ یک واحد به پارامترهای ورودی اضافه کرده سپس آنها را در یکدیگر ضرب کرده و چاپ نموده‌ایم.

در خط یازدهم، ابتدا مقدار متغیرهای valOne$ و valTwo$ را چاپ کرده سپس در خط دوازدهم فانکشن مد نظر را صدا زده و متغیرهای valOne$ و valTwo$ را به عنوان دو پارامتر ورودی به این فانشن پاس داده‌ایم و در خط سیزدهم هم مجدد متغیرهای valOne$ و valTwo$ را چاپ کرده‌ایم. به عنوان خروجی کد فوق خواهیم داشت:

Before calling the function, valOne equals 2 & valTwo equals 7
paramOne + 1 multiplies paramTwo + 1 equals 24
After calling the function, valOne equals 2 & valTwo equals 7

در واقع، داخل فانکشن ما یک واحد به پارامتر وروی paramOne$ اضافه نموده و مقدار آن را برابر با ۳ کرده، یک واحد هم به پارامتر ورودی paramTwo$ اضافه کرده و مقدار آن را برابر با ۸ کرده سپس آنها را در یکدیگر ضرب نموده که حاصل می‌شود ۲۴. می‌بینیم که قبل و بعد از صدا زدن فانکشن مد نظر، مقدار متغیرها بدون تغییر باقی مانده‌اند و به ترتیب برابر با ۲ و ۷ هستند.

وقتی در زبان PHP از متغیرها صحبت می‌کنیم، به خاطر داشته باشیم که وقتی مقداری را به یک متغیر در زبان PHP اختصاص می‌دهیم، سه کار باید صورت گیرد که عبارتند از:
- انتخاب نامی برای متغیر (مثلاً varOne$)
- اختصاص مقدار به متغیر مد نظر (مثلاً ۲)
- اختصاص فضایی از حافظهٔ سیستم به متغیر (که در PHP این کار به صورت خودکار صورت می‌گیرد).

به صورت پیش‌فرض، وقتی که از متغیری به عنوان پارامتر ورودی یک فانکشن استفاده می‌کنیم، ما مقدار (Value) آن متغیر را به فانکشن مربوطه پاس می‌دهیم نه خود متغیر را! به عبارت دیگر، از سه آیتم لیست فوق، صرفاً گزینهٔ دوم (مقدار متغیر) توسط فانکشن دریافت می‌شود.

در چنین شرایطی است که وقتی داخل فانکشن تغییری روی متغیری که به عنوان پارامتر ورودی در نظر گرفته شده صورت می‌گیرد، مقدار متغیر خارج از حوزه (Scope) فانکشن هرگز دستخوش تغییر نخواهد شد.

آشنایی با مفهوم Pass By Reference
حال می‌خواهیم تا به صورت عملی، مفهوم Pass By Reference را متوجه شویم. در حقیقت،‌ اگر بخواهیم به فانکشنی که در بالا ایجاد کردیم این امکان را بدهیم تا قادر گردد مقادیر پارامترهای ورودی را به صورت سراسری تغییر دهد (به عبارت دیگر، حتی خارج از خود فانکشن)، می‌بایست تا پیش از نام پارامترهای ورودی،‌ از علامت Ampersand یا بهتر بگوییم & استفاده کنیم. به عبارت دیگر، به جای آنکه صرفاً Value متغیر مد نظر را به فانکشن پاس دهیم، خود متغیر را پاس می‌دهیم:

$valOne = 2;
$valTwo = 7;

function passByValue(&$paramOne, &$paramTwo)
{
    $paramOne++;
    $paramTwo++;
    echo "paramOne + 1 multiplies paramTwo + 1 equals " . $paramOne * $paramTwo . "
"; } echo "Before calling the function, valOne equals $valOne & valTwo equals $valTwo" . "
"; passByValue($valOne, $valTwo); echo "After calling the function, valOne equals $valOne & valTwo equals $valTwo" . "
";

حال بدون هیچ توضیحی، یک بار دیگر کدهای فوق را اجرا می‌کنیم:

Before calling the function, valOne equals 2 & valTwo equals 7
paramOne + 1 multiplies paramTwo + 1 equals 24
After calling the function, valOne equals 3 & valTwo equals 8

می‌بینیم که در خط اول، به ترتیب مقادیر ۲ و ۷ برای متغیرهای valOne$ و valTwo$ در نظر گرفته شده‌اند؛ سپس داخل فانکشن یک واحد به مقادیر پارامترهای ورودی اضافه نموده (۳ و ۸) و آنها را در یکدیگر ضرب کرده (۲۴) و در خط آخر مجدد مقدار متغیرهای valOne$ و valTwo$ را چاپ کرده‌ایم اما این بار برخلاف اجرای قبل، می‌بینیم که پس از اجرای فانکشن ()passByValue، مقدار متغیرهای valOne$ و valTwo$ به ۳ و ۸ تغییر یافته‌اند!

دلیل این مسئله آن است که ما به صورت Pass By Reference این مقادیر را به فانکشن پاس داده‌ایم؛ به عبارت دیگر، از علامت & قبل از نام پارامترهای ورودی این فانکشن استفاده نموده‌‌ایم و کاری که این علامت انجام می‌دهد، این است که به موتور پی‌اچ‌پی دستور می‌دهد تا تغییرات که داخل فانکشن روی پارامترهای ورودی -یا بهتر بگوییم متغیرهایvalOne$ و valTwo$- صورت می‌گیرد را به صورت سراسری روی متغیرها اعمال کرده و از این پس، مقادیر این متغیرها همان چیزی است که داخل فانکشن تعریف شده است (یعنی مقدار پیش‌فرض ۳ برای پارامتر اول (valOne$) و مقدار پیش‌فرض ۸ برای پارامتر دوم (valTwo$)).

به عبارت دیگر، به طور مثال هم متغیر valOne$ و هم پارامتر paramOne$ به یک فضا در حافظهٔ کامپیوتر اشاره خواهند کرد (به خاطر داشته باشیم به جای Pass By Reference می‌توان از اصطلاح Out Varialble نیز استفاده کرد).

در نظر داشته باشیم که بسیاری از فانکشن‌های از پیش تعریف شدهٔ‌ زبان PHP از ساختار Pass By Reference استفاده می‌کنند. به طور مثال، فانکشن ()sort که این وظیفه را دارا است تا اندیس‌های یک آرایه را مرتب‌سازی کند، پارامتر ورودی‌اش را به عنوان یک رفرنس به آرایهٔ مد نظر تلقی می‌کند.

ارتباط Pass By Reference با Object
پیش از این گفتیم که در زبان برنامه‌نویسی PHP متغیرها و آرایه‌ها به صورت پیش‌فرض اصطلاحاً Pass by Value هستند؛ به عبارت دیگر، تغییرات داخل یک فانکشن نمی‌تواند مقادیر آنها را دستخوش تغییر سازد اما این در حالی است که گفته می‌شود آبجکت‌ها در PHP به صورت پیش‌فرض اصطلاحاً Pass By Reference هستند که می‌شود گفت این گزاره اصلاً درست نیست!

رفرنس در زبان PHP، همان‌طور که قبلاً گفته شد همچون یک Alias (میانبُر) است که این امکان را به وجود می‌آورد تا دو متغیر مختلف به Pointer (پوینتر یا اشاره‌گر) یکسانی از خانهٔ حافظه مرتبط گردند (به کلام ساده، پوینتر به خانه‌های حافظهٔ سیستم اشاره می‌کند). از نسخهٔ پنجم PHP به بعد، متغیری که آبجکتی را در خود ذخیره کرده است دیگر حاوی کلیهٔ اطلاعات آن آبجکت نبوده بلکه حاوی اشاره‌گری به آن آبجکت است که از طریق آن می‌تواند به کلیهٔ اطلاعات آن آبجکت دسترسی پیدا کند.

با این تفاسیر، وقتی ما آبجکتی را به عنوان پارامتر ورودی یک تابع استفاده می‌کنیم و یا آن را به متغیر دیگری نسبت می‌دهیم، متغیرهای مختلف صرفاً یک کپی از اشاره‌گر آن آبجکت را در خود ذخیره می‌کنند که به منظور دستیابی به آن آبجکت مورد استفاده قرار می‌گیرد. برای روشن‌تر شدن این‌ مسئله، مثالی می‌زنیم:

class MyClass {
    public $variable;

    function __construct() 
    {
        $this->variable = "Original";
    }

    public function printVariable() 
    {
          echo $this->variable . "<br>";
    }
}

function changeVariable($obj) 
{
    $obj->variable = "New Value";
    echo $obj->variable . "<br>";
}

$obj = new MyClass();
$obj->printVariable();
changeVariable($obj);
$obj->printVariable();

پیش از تفسیر کدهای فوق، ابتدا یک بار اسکریپت فوق را اجرا می‌کنیم:

Original
New Value
New Value

در تفسیر کدهای فوق بایستی بگوییم که کلاسی ساخته‌ایم تحت عنوان MyClass که حاوی یک پراپرتی از جنس پابلیک (قابل مشاهده در همه جا) تحت عنوان variable$ است. سپس یک کانستراکتور تعریف کرده‌ایم که این وظیفه را دارا است تا به محض ساخته شدن یک آبجکت از روی این کلاس،‌ صدا زده شود و این وظیفه را دارا است تا مقدار استرینگ Original را به تنها پراپرتی این کلاس اختصاص دهد (برای آشنایی بیشتر با مفهوم کانستراکتور، به مقالهٔ آشنایی با مفاهیم Constructor و Destructor در PHP به زبان ساده مراجعه نمایید). در ادامه یک فانکشن تحت عنوان ()printVariable ساخته‌ایم که این وظیفه را دارا است تا به محض صدا زده شدن، مقدار پراپرتی variable$ را پرینت کند.

خارج از این کلاس هم یک فانکشن ساده تحت عنوان ()changeVariable تعریف کرده‌ایم که این وظیفه را دارا است تا پراپرتی variable$ را آپدیت کرده و مقدار New Value را برایش در نظر گیرد و آن را چاپ کند.

حال در خط بیستم و یکم یک آبجکت تحت عنوان obj$ از روی کلاس MyClass ساخته‌ایم و در خط بیست و دوم فانکشن ()printVariable را صدا زده‌ایم که همان‌طور که در خروجی مشخص است، مقدار Original را چاپ کرده است.

در خط بیست و سوم فانکشن ()changeVariable را صدا زده و آبجکتی که از روی کلاس MyClass ساختیم را به آن پاس می‌دهیم؛ کاری که این فانکشن انجام می‌دهد این است که ابتدا مقدار New Value را به پراپرتی variable$ اختصاص داده سپس آن را چاپ می‌کند و در پایان هم یک تگ <br> برای رفتن به خط بعد قرار می‌دهد. در خط دوم از خروجی می‌بینیم که مقدار این فانکشن چاپ شده است.

حال مجدداً در خط بیست و چهارم فانکشن ()printVariable آبجکت obj$ را صدا می‌زنیم و می‌بینیم که در خروجی، مقدار New Value در خط سوم چاپ شده است و اینجا است که متوجه می‌شویم (changeVariable($obj که در خط بیست و سوم نوشتیم، ما در اصل یک رفرنس به آبجکت obj$ ارسال کرده‌ایم و همین می‌شود که مقدار پراپرتی variable$ برای همیشه تغییر می‌یابد.

اساساً وقتی که ما با آبجکت‌ها در PHP کار می‌کنیم، اصلاً نیاز به کپی کردن آبجکت نداریم چرا که فانکشنی که آبجکت مد نظر خود را به آن پاس می‌‌دهیم قرار است کاری روی آن آبجکت اصلی انجام دهد تا در نهایت آبجکتی داشته باشیم که نیازهای ما را مرتفع می‌سازد (در برخی مواقع اگر بخواهیم به معنای واقعی کلمه یک کپی از روی آبجکت خود داشته باشیم، می‌بایست از کلیدواژهٔ clone استفاده نماییم که خارج از مبحث این آموزش است).

همچنین توجه داشته باشیم که وقتی ما از کلیدواژهٔ this$ استفاده می‌کنیم، موتور پی‌اچ‌پی به صورت خودکار یک رفرنس برای ما ایجاد می‌کند. به عبارت دیگر، در کد فوق this$ در خطوط ششم و یازدهم رفرنسی به آبجکت -یا بهتر بگوییم کلاس MyClass- است.

به طور کلی و به گفتهٔ وب‌سایت php.net، تا حد ممکن از ساختار Pass By Reference استفاده نکنید و صرفاً زمانی از این ساختار استفاده کنید که واقعاً به آن نیاز دارید چرا که گاهی‌اوقات این مسئله منجر به ایجاد باگ‌هایی می‌شود که یافتن آنها بسیار دشوار خواهد بود.

در خاتمه، چنانچه علاقمند به فراگیری گام به گام زبان برنامه‌نویسی PHP هستید، می‌توانید به دورهٔ آموزش PHP در سکان آکادمی مراجعه نمایید.