نطاق المتغيرات في PHP

من موسوعة حسوب
< PHP
مراجعة 15:38، 28 يناير 2018 بواسطة عبد اللطيف ايمش (نقاش | مساهمات) (استبدال النص - '\[\[تصنيف:(.*)\]\]' ب'{{SUBPAGENAME}}')

النطاق (scope) هو السياق الذي تعرّف ضمنه المتغيرات، وفي معظم الحالات تمتلك جميع متغيرات PHP نطاقًا واحدًا فقط، ويمتد هذا النطاق ليشمل الملفات المضمّنة (included) والمطلوبة (required) كذلك. فمثلاً:

<?php
$a = 1;
include 'b.inc';
?>

هنا سيكون المتغير ‎$a متوفّراً ضمن شيفرة b.inc المضمّنة، ولكن يصبح النطاق ضمن الدوال المعرّفة من طرف المستخدم نطاقًا محلّيًا (local scope)، وتكون جميع المتغيرات المستخدمة داخل الدالة محدّدة بصورة تلقائية ضمن النطاق المحلي. مثلًا:

<?php
// نطاق عام 
$a = 1;

function test()
{ 
    // الإشارة إلى متغير ضمن نطاق محلّي
    echo $a;
} 

test();
?>

لن تنتج الشيفرة السابقة أي مخرجات؛ ذلك لأنّ عبارة echo تشير إلى نسخة محلّية من المتغير ‎$a الذي لم تُسنَد إليه أيّ قيمة ضمن النطاق المحلي. قد تلاحظ أنّ هذا الأمر مختلف قليلًا عن لغة C، إذ إنَّ المتغيرات العامة في لغة C تكون متاحةً للدوال تلقائيًا إلا في حال إعادة تعريف قيمة المتغير ضمن نطاق محلي. قد يتسبب هذا الأمر في حدوث بعض المشكلات، إذ من الممكن تغيير قيمة المتغيّر العام دون قصد. في PHP يجب التصريح عن كون المتغيّرات عامة داخل الدالة إن كان الغرض هو استخدام تلك المتغيّرات داخل الدالة.

الكلمة المفتاحية global

لنأخذ في البداية مثالًا على استخدام global:

المثال 1: استخدام global

<?php
$a = 1;
$b = 2;

function Sum()
{
    global $a, $b;

    $b = $a + $b;
} 

Sum();
echo $b;
?>

مخرجات الشيفرة السابقة هي 3. فعند التصريح أنَّ المتغيرين ‎$a و ‎$b متغيّران عامّان داخل الدالة، فإن جميع الإشارات لهذين المتغيرين ستُشير إلى المتغيّر العام. ليس هناك عدد محدّد للمتغيرات العامّة التي يمكن لدالة معيّنة أن تتعامل معها.

هناك طريقة ثانية للوصول إلى المتغيّرات الموجودة ضمن النطاق العام وهي استخدام المصفوفة الخاصة ‎$GLOBALS والمعرفة مسبقًا في PHP. يمكن إعادة كتابة المثال السابق كما يلي:

المثال 2: استخدام ‎$GLOBALS بدلًا عن global

<?php
$a = 1;
$b = 2;

function Sum()
{
    $GLOBALS['b'] = $GLOBALS['a'] + $GLOBALS['b'];
} 

Sum();
echo $b;
?>

المصفوفة $GLOBALS هي مصفوفة ترابطية (associative array) يمثّل المفتاح فيها اسم المتغيّر العام أما قيمة عنصر المصفوفة فتمثّل محتويات ذلك المتغيّر. لاحظ كيف أنّ المصفوفة $GLOBALS موجودة في جميع النطاقات؛ وذلك لأنّ المصفوفة ‎$GLOBALS من ذوات النطاق العام العالي (superglobal). إليك مثالًا يوضّح قوّة ذوات النطاق العام العالي.

المثال 3: مثال يوضّح العلاقة بين المجال (scope) وذوات النطاق العام العالي

<?php
function test_global()
{
// معظم المتغيرات المعرّفة مسبقًا ليست "عالية" وتتطلّب استخدام الكلمة المفتاحية
// global
//  ضمن النطاق المحلّي للدالة.
    global $HTTP_POST_VARS;
    
    echo $HTTP_POST_VARS['name'];

    echo $_POST['name'];
}
?>

لاحظ في المثال السابق أنّ ذوات النطاق العام العام العالي متاحة ضمن جميع النطاقات ولا تتطلّب استخدام 'global'. لاحظ كذلك أنّ ذوات النطاق العالي أصبحت متاحة منذ الإصدار 4.1.0 من اللغة وأن HTTP_POST_VARS قد أصبحت مهملة في الوقت الحاضر.

ملاحظة:ليس من الخطأ استخدام الكلمة المفتاحية global خارج الدالة، ويمكن استخدامها في حال تضمين الملفّ داخل الدالة.

استخدام المتغيرات الساكنة

المتغيرات الساكنة (static variables) هي من الخصائص المهمّة في نطاق المتغيرات، فهذه المتغيرات موجودة فقط ضمن النطاق المحلي الخاص بالدالة، ولكنّها لا تفقد قيمتها عند الانتهاء من تنفيذ الشيفرة ضمن هذا النطاق. لاحظ المثال التالي:

المثال 4: مثال يوضّح الحاجة إلى استخدام المتغيّرات الساكنة

<?php
function test()
{
    $a = 0;
    echo $a;
    $a++;
}
?>

هذه الدالة عديمة الفائدة، ففي كلّ مرة تستدعى فيها يأخذ المتغير ‎$a القيمة 0 وتطبع الدالة الرقم 0. أما التعبير ‎$a++‎‎ والذي يزيد مقدار المتغيّر فلا يؤدي أي وظيفة هنا فبمجرد الخروج من الدالة يختفي المتغيّر ‎$a. للحصول على دالة تعدّ الأرقام بصورة صحيحة يجب التصريح عن كون المتغيّر ‎$a متغيّرًا ساكنًا:

المثال 5: مثال عن استخدام المتغيرات الساكنة

<?php
function test()
{
    static $a = 0;
    echo $a;
    $a++;
}
?>

والآن يهيّئ المتغيّر ‎$a عند أوّل استدعاء للدالة فقط، بعدها ستطبع الدالة قيمة هذا المتغيّر وتزيد قيمته في كلّ مرة تستدعى فيها الدالة.

تتيح المتغيرات الساكنة أيضًا وسيلة للتعامل مع الدوال التعاودية (recursive). التعاودية التكرارية هي الدالة التي تستدعي نفسها. يجب توخّي الحذر عند كتابة الدوالّ التعاودية، إذ من الممكن أن يستمر التكرار إلى ما لا نهاية. يجب التأكد من وجود طريقة ملائمة لإيقاف عملية التكرار. في المثال التالي تحسب الدالة التعاودية البسيطة إلى الرقم 10 وذلك بواسطة المتغيّر الساكن ‎$count لمعرفة متى يجب التوقف:

المثال 6: استخدام المتغيرات الساكنة في الدوال التعاودية

<?php
function test()
{
    static $count = 0;

    $count++;
    echo $count;
    if ($count < 10) {
        test();
    }
    $count--;
}
?>

ملاحظة: يمكن التصريح عن المتغيرات الساكنة كما هو ظاهر في المثال السابق. وفي الإصدار 5.6 وما بعده من PHP يمكن إسناد القيم إلى هذه المتغيرات والتي تكون عبارة عن نتيجة لتعبير معين، ولكن لا يمكن إسناد الدوال إلى هذه المتغيّرات وسيتسبب ذلك في خطأ إعرابي (parse error).

المثال السابع: التصريح عن المتغيّرات الساكنة

<?php
function foo(){
    // صحيح
    static $int = 0; 
    // صحيحٌ في الإصدار 5.6 من اللغة وما بعده
    static $int = 1+2;
    // خطأ (إسناد دالة إلى المتغير الساكن)‏‏‏‏
    static $int = sqrt(121);

    $int++;
    echo $int;
}
?>

ملاحظة: تحلّل عمليات الإفصاح عن المتغيرات الساكنة في وقت البناء (compile-time).

المرجعيات مع المتغيّرات العامة (global) والساكنة (static)

يستخدم الإصدار الأول من محرك Zend والذي يدير الإصدار الرابع من PHP المحدّدين static و global مع المتغيّرات المرجعية. على سبيل المثال، عند استيراد متغيّر عامّ داخل نطاق الدالة باستخدام كلمة global فإنّ هذه المتغيّر سينشئ في الواقع مرجعًا إلى المتغيّر العام، وقد يؤدي هذا الأمر إلى نتائج غير متوقّعة كما هو الحال في المثال التالي:

<?php
function test_global_ref() {
    global $obj;
    $obj = &new stdclass;
}

function test_global_noref() {
    global $obj;
    $obj = new stdclass;
}

test_global_ref();
var_dump($obj);
test_global_noref();
var_dump($obj);
?>

هذه هي مخرجات المثال السابق:

NULL
object(stdClass)(0) {
}

ينطبق السلوك ذاته على عبارة static، فالمرجعيات لا تخزّن بشكل ساكن:

<?php
function &get_instance_ref() {
    static $obj;

    echo 'Static object: ';
    var_dump($obj);
    if (!isset($obj)) {
        // إسناد مرجع إلى متغيّر ساكن
        $obj = &new stdclass;
    }
    $obj->property++;
    return $obj;
}

function &get_instance_noref() {
    static $obj;

    echo 'Static object: ';
    var_dump($obj);
    if (!isset($obj)) {
// إسناد الكائن إلى متغيّر ساكن
        $obj = new stdclass;
    }
    $obj->property++;
    return $obj;
}

$obj1 = get_instance_ref();
$still_obj1 = get_instance_ref();
echo "\n";
$obj2 = get_instance_noref();
$still_obj2 = get_instance_noref();
?>

هذه هي مخرجات المثال السابق:

Static object: NULL
Static object: NULL

Static object: NULL
Static object: object(stdClass)(1) {
["property"]=>
int(1)
}

يوضّح هذا المثال كيف أنّه عند إسناد مرجع إلى متغيّر ساكن فإنّ الدالة ‎&get_instance_ref()‎ غير قادرة على تذكّر قيمة المتغيّر عند استدعائها مرة أخرى.

مصادر