معاملات الدوال في PHP

من موسوعة حسوب
< PHP
(بالتحويل من PHP/typehinting)

يمكن تمرير المعلومات إلى الدوالّ بواسطة قائمة المعاملات argument list، وهي قائمة من التعابير المفصولة بالعلامة (,)، وتُعالج هذه المعاملات من اليسار إلى اليمين.

تدعم اللغة تمرير المعاملات بالقيمة (by value، والطريقة الافتراضية) وبالمرجعية وتدعم كذلك تعيين القيمة الافتراضية للمعاملات، إضافة إلى دعم قوائم المعاملات متغيّرة الأطوال.

المثال 1: تمرير المصفوفات إلى الدوال

<?php
function takes_array($input)
{
    echo "$input[0] + $input[1] = ", $input[0]+$input[1];
}
?>

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

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

ولتمرير معامل الدالة بالمرجعية يمكن إضافة الرمز & إلى بداية اسم المعامل عند تعريف الدالة:

المثال 2: تمرير معاملات الدالة بالمرجعية

<?php
function add_some_extra(&$string)
{
    $string .= 'and something extra.';
}
$str = 'This is a string, ';
add_some_extra($str);
echo $str;
// المخرجات
// 'This is a string, and something extra.'
?>

القيم الافتراضية للمعاملات

يمكن تعريف معاملات ذات قيم افتراضية بطريقة مشابهة للغة C++‎ للمعاملات الأولية (scalar) كما يلي:

المثال 3: استخدام المعاملات الافتراضية في الدوال

<?php
function makecoffee($type = "cappuccino")
{
    return "Making a cup of $type.\n";
}
echo makecoffee();
echo makecoffee(null);
echo makecoffee("espresso");
?>

تعطي الشيفرة السابقة المخرجات التالية:

Making a cup of cappuccino.
Making a cup of .
Making a cup of espresso.

تتيح اللغة أيضًا استخدام المصفوفات والنوع الخاص NULL كقيم افتراضية، فعلى سبيل المثال:

المثال 4: استخدام الأنواع غير الأولية كقيم افتراضية

<?php
function makecoffee($types = array("cappuccino"), $coffeeMaker = NULL)
{
    $device = is_null($coffeeMaker) ? "hands" : $coffeeMaker;
    return "Making a cup of ".join(", ", $types)." with $device.\n";
}
echo makecoffee();
echo makecoffee(array("cappuccino", "lavazza"), "teapot");
?>

يجب أن تكون القيمة الافتراضية تعبيرًا ثابتًا، وليست (على سبيل المثال) متغيّرًا أو تابعًا لصنف أو استدعاءً لدالة.

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

المثال 5: الاستخدام غير الصحيح للقيم الافتراضية في الدوال

<?php
function makeyogurt($type = "acidophilus", $flavour)
{
    return "Making a bowl of $type $flavour.\n";
}
 
echo makeyogurt("raspberry");
// لن تحصل على النتيجة المتوقّعة
?>

تعطي الشيفرة السابقة النتيجة التالية:

Warning: Missing argument 2 in call to makeyogurt() in 
/usr/local/etc/httpd/htdocs/phptest/functest.html on line 41
Making a bowl of raspberry .

والآن قارن ما سبق بالمثال التالي:

المثال 6: الاستخدام الصحيح للقيم الافتراضية في الدوال

<?php
function makeyogurt($flavour, $type = "acidophilus")
{
    return "Making a bowl of $type $flavour.\n";
}
 
echo makeyogurt("raspberry");
// ستحصل على النتيجة المتوقعة
?>

تعطي الشيفرة السابقة المخرجات التالية:

Making a bowl of acidophilus raspberry.

ملاحظة: منذ الإصدار الخامس من اللغة، أصبح من الممكن أن تحمل المعاملات الممرّرة بالمرجعية قيمًا افتراضية.

التصريح عن الأنواع

يعرف التصريح عن الأنواع في الإصدار 5 من اللغة بالإشارة إلى الأنواع type hints.

تسمح خاصية التصريح عن الأنواع للدوال بطلب النوع المحدّد من المعاملات في وقت الاستدعاء، وإن كانت القيمة المستدعاة ذات نوع مغاير، تطلق اللغة خطأ من نوع recoverable fatal في الإصدار 5، أما في الإصدار 7 فترمي اللغة استثناءً من نوع TypeError.

ولتحديد النوع الذي ترغب في التصريح عنه، يجب إضافة اسم النوع قبل اسم المعامل، ويمكن قبول قيم NULL وذلك بتعيين NULL كقيمة افتراضية للمعاملات.

الأنواع المسموح بها

النوع الوصف إصدار PHP الأدنى
اسم الصنف/الواجهة يجب أن يكون المعامل مهيّئًا من اسم الصنف أو الواجهة المعطاة. 5.0.0
self يجب أن يكون المعامل مهيّئاً من نفس الصنف الذي عرّف فيه التابع. يمكن استخدام هذا النوع مع توابع الصنف والكائن. 5.0.0
array يجب أن يكون المعامل مصفوفة. 5.1.0
callable يجب أن يكون المعامل قابلاً للاستدعاء. 5.4.0
bool يجب أن يكون المعامل قيمة منطقية. 7.0.0
floata يجب أن يكون المعامل عددًا عشريًا. 7.0.0
int يجب أن يكون المعامل عددًا صحيحًا. 7.0.0
string يجب أن يكون المعامل سلسلة نصية. 7.0.0
iterable يجب أن يكون المعامل مصفوفة أو مهيئاً من Traversable. 7.1.0

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

<?php
 function test(boolean $param) {}
 test(true);
 ?>

مخرجات الشيفرة السابقة هي:

Fatal error: Uncaught TypeError: Argument 1 passed to test() must be an instance of boolean, boolean given, called in - on line 1 and defined in -:1

أمثلة

المثال 7: مثال بسيط للتصريح من نوع الصنف

<?php
class C {}
class D extends C {}

// هذا الصنف لا يرث الصنف الأول
class E {}

function f(C $c) {
    echo get_class($c)."\n";
}

f(new C);
f(new D);
f(new E);
?>

تعطي الشيفرة السابقة المخرجات التالية:

C
D

Fatal error: Uncaught TypeError: Argument 1 passed to f() must be an instance of C, instance of E given, called in - on line 14 and defined in -:8
Stack trace:
#0 -(14): f(Object(E))
#1 {main}
  thrown in - on line 8

المثال 8: مثال بسيط للتصريح من نوع الواجهة

<?php
interface I { public function f(); }
class C implements I { public function f() {} }

// هذا الصنف لا يعتمد الواجهة
class E {}

function f(I $i) {
    echo get_class($i)."\n";
}

f(new C);
f(new E);
?>

وتكون المخرجات كما يلي:

C

Fatal error: Uncaught TypeError: Argument 1 passed to f() must implement interface I, instance of E given, called in - on line 13 and defined in -:8
Stack trace:
#0 -(13): f(Object(E))
#1 {main}
  thrown in - on line 8

المثال 9: تصريح من نوع Null

<?php
class C {}

function f(C $c = null) {
    var_dump($c);
}

f(new C);
f(null);
?>

يعطي المثال السابق المخرجات التالية:

object(C)#1 (0) {
}
NULL

الأنواع الصارمة

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

يمكن تفعيل الوضع الصارم (strict mode) لكل ملفّ على حدة، وفي هذا النمط يكون النوع المتوقّع فقط هو النوع المقبول، وإلا ستطلق اللغة استثناءً من نوع TypeError. والاستثناء الوحيد لهذه القاعدة هو تمرير عدد صحيح لدالة تتوقع الحصول على عدد عشري. لن تتأثر الدوال المستدعاة من داخل دوالّ أخرى بالتصريحات ذات النمط الصارم.

تستخدم عبارة declare لتفعيل الوضع الصارم مع استخدام التصريح strict_types.

تنبيه: سيؤثّر تفعيل الوضع الصارم على التصريحات من نوع return.

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

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

المثال 10: الأنواع الصارمة

<?php
declare(strict_types=1);

function sum(int $a, int $b) {
    return $a + $b;
}

var_dump(sum(1, 2));
var_dump(sum(1.5, 2.5));
?>

يعطي المثال السابق المخرجات التالية:

int(3)

Fatal error: Uncaught TypeError: Argument 1 passed to sum() must be of the type integer, float given, called in - on line 9 and defined in -:4
Stack trace:
#0 -(9): sum(1.5, 2.5)
#1 {main}
  thrown in - on line 4

المثال 11: الأنواع الضعيفة

<?php
function sum(int $a, int $b) {
    return $a + $b;
}

var_dump(sum(1, 2));

// ستتحوّل إلى أعداد صحيحة، لاحظ المخرجات أدناه.
var_dump(sum(1.5, 2.5));
?>

مخرجات الشيفرة السابقة هي ما يلي:

int(3)
int(3)

المثال 12: التقاط الاستثناء من نوع TypeError

<?php
declare(strict_types=1);

function sum(int $a, int $b) {
    return $a + $b;
}

try {
    var_dump(sum(1, 2));
    var_dump(sum(1.5, 2.5));
} catch (TypeError $e) {
    echo 'Error: '.$e->getMessage();
}
?>

تعطي الشيفرة السابقة النتيجة التالية:

int(3)
Error: Argument 1 passed to sum() must be of the type integer, float given, called in - on line 10

قوائم معاملات متغيرة الأطوال

تدعم لغة PHP قوائم المعاملات متغيرة الأطوال في الدوال المعرّفة من طرف المستخدم. ويمكن الاستفادة من هذه الخاصية بإضافة الرمز ... في الإصدار 5.6 وما بعده، وباستخدام الدوال func_num_args()‎، و func_get_arg()‎ و func_get_args()‎ في الإصدار 5.5 وما قبله.

الرمز ... في الإصدار 5.6 وما بعده

يمكن إضافة الرمز ... إلى قائمة المعاملات في الإصدار 5.6 وما بعده من اللغة للإشارة إلى أن الدالة تتقبّل عددًا متغيّرًا من المعاملات والتي تمرّر على هيئة مصفوفة، كما في المثال التالي:

المثال 13: استخدام ... للوصول إلى المعاملات المتغيرة

<?php
function sum(...$numbers) {
    $acc = 0;
    foreach ($numbers as $n) {
        $acc += $n;
    }
    return $acc;
}

echo sum(1, 2, 3, 4);
?>

يعطي المثال السابق النتيجة التالية:

10

يمكن أيضًا استخدام ... عند استدعاء الدوال لاستخراج عناصر مصفوفة إلى قائمة المعاملات:

المثال 14: استخدام الرمز ... لتقديم المعاملات

<?php
function add($a, $b) {
    return $a + $b;
}

echo add(...[1, 2])."\n";

$a = [1, 2];
echo add(...$a);
?>

يعطي المثال السابق النتيجة التالية:

3
3

يمكن تحديد معاملات موضعية اعتيادية قبل الرمز ...، وفي هذه الحالة ستضاف فقط المعاملات الأخيرة التي لا تتطابق مع المعاملات الموضعية إلى المصفوفة الناشئة من استخدام الرمز ...

يمكن إضافة إشارة للنوع type hint قبل الرمز ... وبهذا يجب أن تكون جميع المعاملات الملتقطة بواسطة ... على هيئة كائنات للصنف المشار إليه.

في المثال التالي لاحظ كيف الشيفرة ستطلق خطأً لأنّ null ليست كائنًا من نوع DateInterval.

المثال 15: معاملات متغيّرة مع الإشارة للنوع

<?php
function total_intervals($unit, DateInterval ...$intervals) {
    $time = 0;
    foreach ($intervals as $interval) {
        $time += $interval->$unit;
    }
    return $time;
}

$a = new DateInterval('P1D');
$b = new DateInterval('P2D');
echo total_intervals('d', $a, $b).' days';

echo total_intervals('d', null);
?>

تعطي الشيفرة السابقة النتيجة التالية:

3 days

Catchable fatal error: Argument 2 passed to total_intervals() must be an instance of DateInterval, null given, called in - on line 14 and defined in - on line 2

وأخيرًا يمكن تمرير معاملات متغيّرة بالمرجعية وذلك بإضافة الرمز (&) قبل الرمز …

الإصدارات الأقدم من PHP

لا حاجة لاستخدام صيغة خاصة للإشارة إلى أنّ الدالة تتقبّل قائمة متغيرة الأطوال من المعاملات، ولكن الوصول إلى معاملات الدالة يكون بواسطة الدوال func_num_args()‎، و func_get_arg()‎ و func_get_args()‎.

يتحوّل المثال الأول أعلاه إلى الصيغة التالية عند استخدام الإصدار 5.5 من اللغة:

المثال 16: الوصول إلى المعاملات المتغيرة في الإصدار 5.5 وما قبله من اللغة

<?php
function sum() {
    $acc = 0;
    foreach (func_get_args() as $n) {
        $acc += $n;
    }
    return $acc;
}

echo sum(1, 2, 3, 4);
?>

تعطي الشيفرة السابقة النتيجة التالية:

10

مصادر