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

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

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

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

آشنایی با مفهوم 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 از متغیرها صحبت می‌کنیم، به خاطر داشته باشیم که وقتی مقداری را به یک متغیر اختصاص می‌دهیم، سه کار باید صورت گیرد که عبارتند از:

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

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

آشنایی با مفهوم Pass By Reference
پیش از هر چیز، باید ببینیم که منظور از Reference چیست که از یک دید کلی می‌توان تعریف زیر را مبنا قرار داد:

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

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

$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 در ارتباط با کلاس و آبجکت
پیش از این گفتیم که در زبان برنامه‌نویسی PHP متغیرها و آرایه‌ها به صورت پیش‌فرض اصطلاحاً Pass by Value هستند. به عبارت دیگر، تغییرات داخل یک فانکشن نمی‌تواند مقادیر آن‌ها را دستخوش تغییر سازد اما این در حالی است که گفته می‌شود آبجکت‌ها در PHP به صورت پیش‌فرض اصطلاحاً Pass By Reference هستند که می‌شود گفت این گزاره اصلاً درست نیست!

رفرنس در زبان PHP، همان‌طور که قبلاً گفته شد، همچون یک میانبُر است که این امکان را به وجود می‌آورد تا دو متغیر مختلف به پوینتر (اشاره‌گر) یکسانی از خانهٔ حافظه مرتبط گردند (به کلام ساده، پوینتر به خانه‌های حافظهٔ سیستم اشاره می‌کند.) از نسخهٔ پنجم 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 که در خط بیست‌وسوم نوشتیم، ما در اصل از روش Pass By Reference استفاده کرده‌ایم و همین می‌شود که مقدار پراپرتی variable$ برای همیشه تغییر می‌یابد.

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

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

از بهترین نوشته‌های کاربران سکان آکادمی در سکان پلاس


online-support-icon