الدوال المجهولة في PHP

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

الدوال المجهولة (anonymous functions) التي تُعرَف أيضًا بالمصطلح (closures) تسمح بإنشاء دالة ليس لها اسم محدد. غالبًا ما تستخدم هذه الدوال للحصول على قيمتها كمعاملات استدعاء (راجع callback) ولها استخدامات أخرى.

تطبق اللغة الصنف Closure لاستخدام الدوال المجهولة.

المثال 1: الدوال المجهولة

<?php
echo preg_replace_callback('~-([a-z])~', function ($match) {
    return strtoupper($match[1]);
}, 'hello-world');
// outputs helloWorld
?>

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

المثال 2: مثال على إسناد دالة مجهولة إلى متغير

<?php
$greet = function($name)
{
    printf("Hello %s\r\n", $name);
};

$greet('World');
$greet('PHP');
?>

يمكن للدوال المجهولة أن ترث المتغيرات من النطاق الأب، ويجب تمرير هذه المتغيرات إلى الدالة المجهولة باستخدام البنية use. منذ الإصدار 7.1 من اللغة، أصبح من الواجب أن لا تتضمّن هذه المتغيرات متغيراتٍ من ذوات النطاق العام العالي superglobals، أو ‎$this أو متغيرات تحمل نفس أسماء المعاملات.

المثال 3: وراثة المتغيرات من النطاق الأب

<?php
$message = 'hello';

// دون استخدام البنية use
$example = function () {
    var_dump($message);
};
$example();

// وراثة المتغير
$example = function () use ($message) {
    var_dump($message);
};
$example();

// تؤخذ قيمة المتغير المورثة عند تعريف الدالة
// وليس عند استدعائها
$message = 'world';
$example();

// Reset message
$message = 'hello';

// وراثة القيمة بالمرجعية
$example = function () use (&$message) {
    var_dump($message);
};
$example();

// تبدّلت القيمة داخل استدعاء الدالة
// بعد تغيير القيمة في النطاق الأب
$message = 'world';
$example();

// يمكن للدوال المجهولة أن تأخذ معاملات اعتيادية

$example = function ($arg) use ($message) {
    var_dump($arg . ' ' . $message);
};
$example("hello");
?>

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

Notice: Undefined variable: message in /example.php on line 6
NULL
string(5) "hello"
string(5) "hello"
string(5) "hello"
string(5) "world"
string(11) "hello world"

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

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

class Cart
{
    const PRICE_BUTTER  = 1.00;
    const PRICE_MILK    = 3.00;
    const PRICE_EGGS    = 6.95;

    protected $products = array();
    
    public function add($product, $quantity)
    {
        $this->products[$product] = $quantity;
    }
    
    public function getQuantity($product)
    {
        return isset($this->products[$product]) ? $this->products[$product] :
               FALSE;
    }
    
    public function getTotal($tax)
    {
        $total = 0.00;
        
        $callback =
            function ($quantity, $product) use ($tax, &$total)
            {
                $pricePerItem = constant(__CLASS__ . "::PRICE_" .
                    strtoupper($product));
                $total += ($pricePerItem * $quantity) * ($tax + 1.0);
            };
        
        array_walk($this->products, $callback);
        return round($total, 2);
    }
}

$my_cart = new Cart;

// إضافة بعض العناصر إلى عربة التسوق
$my_cart->add('butter', 1);
$my_cart->add('milk', 3);
$my_cart->add('eggs', 6);

// عرض السعر الإجمالي مع إضافة ضريبة مبيعات بنسبة 5%

print $my_cart->getTotal(0.05) . "\n";

// الناتج هو 54.29
?>

المثال 5: ربط ‎$this التلقائي

<?php

class Test
{
    public function testing()
    {
        return function() {
            var_dump($this);
        };
    }
}

$object = new Test;
$function = $object->testing();
$function();
    
?>

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

object(Test)#1 (0) {
}

أما مخرجات الشيفرة السابقة في الإصدار 5.3 من اللغة فهي:

Notice: Undefined variable: this in script.php on line 8
NULL

منذ الإصدار 5.4.0، عند التصريح عن الصنف الحالي ضمن صنف آخر، يرتبط الصنف الحالي به بصورة تلقائية، وبهذا يمكن استخدام المتغير ‎$this داخل نطاق الدالة. إن لم ترغب في حصول عملية الربط التلقائي هذه، يمكنك حينئذٍ استخدام دالة مجهولة ساكنة.

الدوال المجهولة الساكنة

منذ الإصدار 5.4 من اللغة، أصبح بالإمكان التصريح عن الدوال المجهولة الساكنة، وبهذا يمكن تجنب الربط التلقائي بين الصنف الحالي والدوال المجهولة، كذلك يمكن للكائنات أن لا ترتبط بهذه الدوال في وقت التشغيل.

المثال 6: محاولة استخدام ‎$this داخل دالة مجهولة ساكنة

<?php

class Foo
{
    function __construct()
    {
        $func = static function() {
            var_dump($this);
        };
        $func();
    }
};
new Foo();

?>

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

Notice: Undefined variable: this in %s on line %d
NULL

المثال 7: محاولة ربط كائن بدالة مجهولة ساكنة

<?php

$func = static function() {
    // function body
};
$func = $func->bindTo(new StdClass);
$func();

?>

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

Warning: Cannot bind an instance to a static closure in %s on line %d

ملاحظة: يمكن استخدام الدوال func_num_args()‎ و func_get_arg()‎ و func_get_args()‎ داخل الدوال المجهولة.

سجل التغييرات

الإصدار الوصف
7.1.0 لا يمكن أن ترث الدوال المجهولة المتغيرات ذوات النطاق العام العالي، أو ‎$this أو أي متغيّر يحمل أحد أسماء المعاملات.
5.4.0 يمكن أن تستخدم الدوال المجهولة المتغير ‎$this، ويمكن للدوال المجهولة أن تكون ساكنة.
5.3.0 أصبحت الدوال المجهولة متوفّرة في اللغة.

مصادر