المراجع في PHP

من موسوعة حسوب
< PHP

المراجع (references) في PHP هي وسيلة للوصول إلى محتوى المتغير نفسه باستخدام أسماء مختلفة، والمراجع لا تشبه المؤشّرات في لغة C، فعلى سبيل المثال لا يمكن إجراء العمليات الحسابية الخاصّة بالمؤشّرات على المراجع، وهي ليست عناوين ذاكرة حقيقية، وهكذا. راجع الصفحة "ما هو غير المقصود بالمراجع" للمزيد من المعلومات.

المراجع هي أسماء بديلة رمزية مجدولة (Symbol table aliases). لاحظ أنّ اسم المتغير ومحتواه مختلفان في لغة PHP؛ لهذا يمكن لنفس المحتوى أن يحمل اسمين مختلفين. وأقرب مثال على ذلك هو أنظمة الملفات في يونكس، فأسماء المتغيرات تشبه المجلدات، ومحتوى المتغير يشبه الملف نفسه، ويمكن تشبيه المراجع بالوصلات الصلبة (hard links) في أنظمة الملفات المستخدمة في يونكس.

ما الذي يمكن للمراجع أن تقوم به؟

هناك ثلاث عمليات أساسية التي يمكن أداؤها باستخدام المراجع: الإسناد بالمرجعية، التمرير بالمرجعية، الإعادة بالمرجعية. يقدّم هذا القسم من الدليل مقدّمة عن هذه العمليات، إضافة إلى روابط للإطلاع بصورة أوسع.

الإسناد بالمرجعية

تتيح المراجع في PHP جعل متغيّرين يشيران إلى المحتوى نفسه، بمعنى أنّه عند كتابة:

<?php
$a =& $b;
?>

فهذا يعني أنّ المتغيّرين ‎$a و ‎$b يشيران إلى المحتوى نفسه.

ملاحظة: المتغيّران ‎$a و ‎$b متساويان هنا بالكامل، ولا يشير المتغير ‎$a إلى ‎$b أو العكس، بل يشير كلا المتغيّرين إلى المكان ذاته.

ملاحظة: في حال إسناد أو تمرير أو إعادة متغيّر غير معرّف بالمرجعية، فإنّه اللغة تنشئ ذلك المتغيّر.

المثال 1: استخدام المراجع مع متغيّرات غير معرّفة

<?php
function foo(&$var) { }
foo($a); // $a is "created" and assigned to null
$b = array();
foo($b['b']);
var_dump(array_key_exists('b', $b)); // bool(true)
$c = new StdClass;
foo($c->d);
var_dump(property_exists($c, 'd')); // bool(true)
?>

يمكن استخدام الصيغة نفسها مع الدوال التي تعيد بالمرجعية، ومع العامل new (منذ الإصدار 4.0.4 و قبل الإصدار 5.0.0 من PHP):

<?php
$foo =& find_var($bar);
?>

منذ الإصدار الخامس من اللغة، تعيد الكلمة المفتاحية new مرجعًا بصورة تلقائية؛ لذا فإنّ استخدام ‎‎‎‎‎=&‎‎‎‎‎ في هذا السياق أصبح مهملًا وسيطلق رسالة من نوع E_DEPRECATED في الإصدار 5.3 وما بعده من PHP، ورسالة من نوع E_STRICT في الإصدارات الأقدم. أما في الإصدار السابع من PHP أصبحت هذه الصيغة غير صحيحة. (الفرق هنا من الناحية التقنية هو أنّ متغيرات الكائنات في الإصدار الخامس هي أشبه بالمصادر وهي ليست سوى مؤشر لبيانات الكائن الحقيقية؛ لذا فمراجع الكائنات هذه ليست "مراجع" بنفس المفهوم المستخدم سابقًا (أسماء بديلة). للاطلاع بصورة أوسع على هذا الموضوع راجع الكائنات والمراجع).

تحذير: في حال إسناد مرجع إلى متغيّر عام (global) داخل دالّة ما، فإنّ المرجع سيكون مرئيًا داخل الدالة فقط. يمكن تجنّب هذا السلوك باستخدام مصفوفة ‎$GLOBALS.

المثال 2: الإشارة إلى متغيّر عام داخل الدوال

<?php
$var1 = "Example variable";
$var2 = "";
function global_references($use_globals)
{
    global $var1, $var2;
    if (!$use_globals) {
        $var2 =& $var1; // visible only inside the function
    } else {
        $GLOBALS["var2"] =& $var1; // visible also in global context
    }
}
global_references(false);
echo "var2 is set to '$var2'\n"; // var2 is set to ''
global_references(true);
echo "var2 is set to '$var2'\n"; // var2 is set to 'Example variable'
?>

انظر إلى المتغيّر العام ‎$var كاختصار للتعبير ‎$var =& $GLOBALS['var'];‎ لذا فإنّ إسناد مرجع آخر إلى ‎$var سيغيّر المرجع المحليّ فقط.

ملاحظة: في حال إسناد قيمة إلى متغيّر يمتلك مراجع في حلقة foreach، سيطرأ التعديل على المراجع أيضًا.

المثال 3: المراجع وعبارة foreach

<?php
$ref = 0;
$row =& $ref;
foreach (array(1, 2, 3) as $row) {
    // بعض الشيفرة هنا
}
echo $ref;
// 3- آخر عنصر في المصفوفة التي جرى المرور على عناصرها
?>

يمكن للتعابير التي تنشئها البنية array()‎ أن تسلك سلوك الإسناد بالمرجعية -ليس بصورة تامة- وذلك بإلحاق الرمز & بعنصر المصفوفة المراد إضافته. مثال:

<?php
$a = 1;
$b = array(2, 3);
$arr = array(&$a, &$b[0], &$b[1]);
$arr[0]++; $arr[1]++; $arr[2]++;
/* $a == 2, $b == array(3, 4); */
?>

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

<?php
// إسناد متغيّرات أولية
$a = 1;
$b =& $a;
$c = $b;
$c = 7; 
// هذا المتغير ليس مرجعًا، ولن يطرأ أي تغيير على المتغيرين الأول والثاني
// إسناد متغيرات المصفوفة
$arr = array(1);
$a =& $arr[0];
// المتغير وعنصر المصفوفة موجودان في نفس مجموعة المرجع
$arr2 = $arr;
// ليست عملية إسناد بالمرجعية
$arr2[0]++;
/* $a == 2, $arr == array(2) */
// يتغير محتوى المتغيّر الثاني مع أنّه ليس مرجعًا
?>

بعبارة أخرى، يعرّف سلوك المرجعية في المصفوفات في كلّ عنصر على حدة، ويكون هذا السلوك في معزل عن حالة المرجع التابع للمصفوفة الحاوية.

التمرير بالمرجعية

الأمر الثاني الذي يمكن للمراجع أن تقوم به هو أن تمرّر المتغيّرات بالمرجعية، ويتمّ ذلك بإنشاء متغيّر محلّي في دالّة ومتغيّر في النطاق المستدعي يشير إلى المحتوى ذاته. مثال:

<?php
function foo(&$var)
{
    $var++;
}
$a=5;
foo($a);
?>

ستصبح قيمة المتغير ‎$a في المثال السابق 6. يحدث هذا بسبب أنّ المتغيّر ‎$var في الدالة foo يشير إلى نفس المحتوى الذي يشير إليه المتغير ‎$a. للاطلاع بصورة أوسع على هذا الموضوع، راجع صفحة التمرير بالمرجعية.

الإعادة بالمرجعية

الأمر الثالث الذي يمكن للمراجع القيام به هو أن تعيد بالمرجعية.

‎‎ما هو غير المقصود بالمراجع؟

كما ذكرنا سابقًا، فإنّ المراجع ليست مؤشّرات pointers، وهذا يعني أنّ البنية في المثال التالي لن تقوم بما هو متوقع منها:

<?php
function foo(&$var)
{
    $var =& $GLOBALS["baz"];
}
foo($bar); 
?>

ما يحدث هنا هو أنّ المتغير ‎$var في الدالة foo سيرتبط بالمتغيّر ‎$bar في عبارة الاستدعاء، ولكنّه سيرتبط مرة أخرى مع ‎$GLOBALS["baz"]‎. وليست هناك أيّ طريقة لربط المتغير ‎$bar في النطاق المستدعي بشيء آخر عن طريق آلية المرجعية؛ لأنّ ‎$bar غير متوفّر ضمن الدالة foo (صحيح أنّ المتغيّر ‎$var يمثّله، ولكنّ ‎$var يمتلك محتوى فقط وليس رابطة اسم مع قيمة في جدول رموز الاستدعاء). يمكن استخدام ميزة الإعادة بالمرجعية للإشارة إلى المتغيّر المحدّد من قبل الدالة.

التمرير بالمرجعية

يمكن تمرير المتغيّرات بالمرجعية إلى الدالة لتتمكّن الدالة من تعديل المتغيّر، والمثال التالي يوضّح صيغة التمرير:

<?php
function foo(&$var)
{
    $var++;
}
$a=5;
foo($a);
// يحمل المتغير هنا القيمة 6
?>

ملاحظة: لا تستخدم العلامة & في عبارة استدعاء الدالة، وتستخدم فقط في عبارة التعريف. عند استخدام العلامة & في عبارة الاستدعاء مثل ‎ foo(&$a)‎في الإصدار 5.3.0 من PHP فإنّك ستتلقّى تحذيرًا مفاده أنّ التمرير بالمرجعية في جملة الاستدعاء أصبح مهملًا، وفي الإصدار 5.4.0 يؤدي استخدام هذا الرمز إلى إطلاق خطأ من نوع fatal؛ وذلك لأنّ هذه الطريقة قد حذفت في هذا الإصدار.

يمكن تمرير الأمور التالية بالمرجعية:

  • المتغيرات، مثل foo($a)
  • عبارات new، مثل foo(new foobar())
  • المراجع المعادة من الدوال، مثل:
<?php
function foo(&$var)
{
    $var++;
}
function &bar()
{
    $a = 5;
    return $a;
}
foo(bar());
?>

اطلع على المزيد حول الإعادة بالمرجعية.

لا يمكن تمرير تعابير أخرى بالمرجعية وتكون النتيجة غير معرّفة. فعلى سبيل المثال، الأمثلة التالية غير صحيحة:

لاحظ أنّ الاستدعاء foo(bar());‎ ينتج عنه خطأ من نوع fatal في الإصدار 5.0.5 من اللغة، وملاحظة من النوع strict standards في الإصدار 5.1.1 وملاحظة في الإصدار 7.0.0 وما بعده، أما الاستدعاء foo(5);‎ فينتج عنه خطأ من النوع fatal.

<?php
function foo(&$var)
{
    $var++;
}
function bar() 
// لاحظ عدم استخدام رمز المرجعية
{
    $a = 5;
    return $a;
}
foo(bar());
foo($a = 5);
// تعبير وليس متغيّرًا
foo(5);
?>

الإعادة بالمرجعية

تكون الإعادة بالمرجعية مفيدة عندما ترغب في استخدام دالة لمعرفة المتغيّر الذي يجب أن يرتبط المرجع معه، ولكن لا تحاول استخدام الإعادة بالمرجعية لزيادة معدّل الأداء، فمحرّك اللغة سيقوم بذلك بنفسه، ولا تُعِد المراجع إلا إذا كان هناك سبب تقنيّ للقيام بذلك. لإجراء عملية الإعادة بالمرجعية استخدم الصيغة التالية:

<?php
class foo {
    public $value = 42;
    public function &getValue() {
        return $this->value;
    }
}
$obj = new foo;
$myValue = &$obj->getValue(); //  42
$obj->value = 2;
echo $myValue;                // 2
?>

في هذا المثال ستُعيّن خاصّية الكائن والمعادة بواسطة الدالة getValue وليست النسخة كما هو الحال عند عدم استخدام صيغة المرجعية.

ملاحظة: بخلاف عملية تمرير المعاملات، يجب استخدام الرمز & هنا في كلا المكانين، وذلك للإشارة إلى أنّك ترغب في الإعادة بالمرجعية ولا ترغب في إنشاء نسخة، وللإشارة إلى أنّك ترغب في إجراء عملية الربط بالمرجعية بدلًا من عملية الإسناد الاعتيادية على المتغيّر ‎‎$myValue.

ملاحظة: إن حاولت إعادة مرجع من دالة باستخدام الصيغة: return($this->value);‎ فإنّ ذلك لن يعمل؛ لأنّك تحاول إعادة نتيجة تعبير وليس متغير بالمرجعية. يمكنك فقط إعادة المتغيّرات بالمرجعية من الدوال ولا شيء آخر. منذ الإصدار 5.1.0 من PHP تطلق اللغة خطأً من نوع E_NOTICE إن حاولت الشيفرة إعادة تعبير ديناميكي أو نتيجة العامل new.

لاستخدام المرجع المُعاد يجب اللجوء إلى عملية الإسناد بالمرجعية:

<?php
function &collector() {
  static $collection = array();
  return $collection;
}
$collection = &collector();
$collection[] = 'foo';
?>

ولتمرير المرجع المعاد إلى دالة أخرى يمكنك استخدام الصيغة التالية:

<?php
function &collector() {
  static $collection = array();
  return $collection;
}
array_push(collector(), 'foo');
?>

ملاحظة: لاحظ أنّ التعبير array_push(&collector(), 'foo');‎ لن يعمل، وأنّ النتيجة هي خطأ من نوع fatal.

إلغاء تعيين المراجع

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

<?php
$a = 1;
$b =& $a;
unset($a); 
?>

لن تلغي الشيفرة تعيين المتغير ‎‎$b، وإنما ‎$a فقط. من المفيد مرة أخرى التفكير بالأمر على أنّه مماثل لاستدعاء unlink في أنظمة يونكس.

الكشف عن المراجع

تُطبّق معظم بنى الصيغ في PHP بواسطة آلية المرجعية؛ لذا ينطبق كلّ ما يُذكر هنا حول ربط المراجع على هذه البنى أيضًا. تكلمنا عن بعض البنى مثل التمرير والإعادة بالمرجعية سابقًا، وإليك بعض البنى التي تستخدم المراجع:

المراجع العامة

عند التصريح عن متغير عام فإنّك في الواقع تصنع مرجعًا لمتغيّر عام. وهذا يعني أنّ ذلك مماثل لما يلي:

<?php
$var =& $GLOBALS["var"];
?>

هذا يعني أنّ إلغاء تعيين المتغيّر ‎‎$var لن يؤدي إلى إلغاء تعيين المتغيّر العام.

المتغيّر الزائف ‎$this

يكون المتغيّر الزائف ‎$this في توابع الكائنات مرجعًا للكائن المستدعي دائمًا.

مصادر