اذهب إلى المحتوى

التوابع السحرية (Magic Methods) في PHP


سارة محمد2

‎call()‎__ و‎callStatic()‎__

يُستدعى التابعين ‎__call()‎ و‎__callStatic()‎ عندما تريد استدعاء تابع كائن غير موجود في سياق تابع أو سياق ساكن.

في الشيفرة التالية يُستدعى التابع ‎__call()‎ عندما يحاول شخص ما استدعاء تابع في سياق كائن غير موجود مثل ‎$foo->method($arg, $arg1);‎، سيحتوي الوسيط الأول على اسم التابع وهو method في مثالنا وسيحتوي الوسيط الثاني على قيم ‎$arg و‎$arg1 كمصفوفة.

ويُستدعى التابع ‎__callStatic()‎ من محتوى ساكن عند استدعاء تابع ساكن غير موجود، مثلًا Foo::buildSomethingCool($arg);‎، سيحتوي الوسيط الأول على اسم التابع وهو buildSomethingCool في مثالنا ويحتوي الوسيط الثاني على قيمة ‎$arg في مصفوفة. لاحظ أنّ بصمة هذا التابع مختلفة (يتطلب الكلمة المفتاحية static)، هذا التابع لم يكن موجودًا قبل الإصدار PHP 5.3.

class Foo
{
    public function __call($method, $arguments)
    {
        // (1)
        $snakeName = CaseHelper::camelToSnake($method);

        // الحصول على البادئة
        $subMethod = substr($snakeName, 0, 3);

        // إسقاط اسم التابع
        $propertyName = substr($snakeName, 4);

        switch ($subMethod) {
            case "get":
                return $this->data[$propertyName];
            case "set":
                $this->data[$propertyName] = $arguments[0];
                break;
            case "has":
                return isset($this->data[$propertyName]);
            default:
                throw new BadMethodCallException("Undefined method $method");
        }
    }

    public static function __callStatic($method, $arguments)
    {
    // يمكن استخدام هذا التابع عندما تحتاج شيء ما مثل مصنع عام أو شيء ما آخر
        print_r(func_get_args());
    }
}

في الموضع (1) نفعل شيء ما مع المعلومات مثل زيادة التحميل أو شيء عام، فمثلًا لنفرض أننا نكتب صنفًا عامًا يحمل بعض البيانات ويسمح للمستخدم بأن يجلبها ويضبطها باستخدام التوابع الجالب والضابط، وكان لدينا الصنف CaseHelper يساعد على تحويل نمط سنام الجمل (camelCase) إلى نمط الثعبان (snake_case). هذا التابع مبسّط لذا لا يتحقق من صلاحية الاسم أو عدمها.

مثال:

$instance = new Foo();
$instance->setSomeState("foo");

var_dump($instance->hasSomeState()); // bool(true)
var_dump($instance->getSomeState()); // string "foo"

Foo::exampleStaticCall("test");

/*
Array
(
    [0] => exampleCallStatic
    [1] => test
)
*/

‎get()‎__ و‎__set()‎ و‎__isset()‎ و‎__unset()‎

كلما حاولت استعادة حقل معين من صنف كما في الشيفرة:

$animal = new Animal();
$height = $animal->height;

تستدعي PHP التابع السحري ‎__get($name)‎ ويكون ‎$name هو "height" في حالتنا. كتابة حقل ما من الصنف كالتالي:

$animal->height = 10;

ستستدعي التابع السحري ‎__set($name, $value),‎ ويكون ‎$name هو "height" و‎$value هو 10.

لدينا في PHP أيضًا تابعين مدمجين هما ‎isset()‎ الذي يتحقق من وجود متغير، وunset‎()‎ الذي يدمر متغير.

إنّ الشيفرة التالية:

isset($animal->height);

ستستدعي الدالة ‎__isset($name)‎ على ذلك الكائن، كما أنّ إزالة تعيين متغير كما في الشيفرة:

unset($animal->height);

ستستدعي الدالة ‎__unset($name)‎ على ذلك الكائن.

تستعيد PHP الحقل كما هو مخزن في الصنف عندما لاتعرّف هذه التوابع في صنفك، لكن يمكنك تجاوز هذه التوابع لإنشاء أصناف يمكنها أن تحمل بيانات مثل المصفوفة لكنها قابلة للاستخدام ككائن:

class Example {
    private $data = [];

    public function __set($name, $value) {
        $this->data[$name] = $value;
    }

    public function __get($name) {
        if (!array_key_exists($name, $this->data)) {
            return null;
        }
        return $this->data[$name];
    }

    public function __isset($name) {
        return isset($this->data[$name]);
    }

    public function __unset($name) {
        unset($this->data[$name]);
    }
}

$example = new Example();

// وقيمتها 15 $data في المصفوفة 'a' تخزين
$example->a = 15;

// $data من المصفوفة 'a' استعادة مفتاح المصفوفة
echo $example->a; // 15

// null محاولة استعادة مفتاح غير موجود في المصفوفة تُعيد
echo $example->b;
// لا يطبع شيء

if (isset($example->a)) {
    unset($example->a));
}

الدالة empty()‎‍ والتوابع السحرية
لاحظ أنّ استدعاء الدالة empty()‎ على خاصيّة صنف ستستدعي الدالة ‎__isset($name)‎ حسب ما ورد في توثيق PHP: ‏empty()‎ هي المكافئ المختصر للشيفرة:

!isset($var) || $var == false

‎__construct()‎ و‎__destruct()‎

‎__construct()‎ هو التابع السحري الأشهر في PHP لأنّه يُستخدم لضبط صنف عند تهيئته، أما التابع العكسي له ‎__destruct()‎ فإنّه يُستدعى عندما لا يوجد مراجع متبقية للكائن الذي أنشأته أو عندما تفرض حذفه وعندها ستقوم مجموعة المهملات بتنظيف الكائن عن طريق استدعاء الهادم أولًا ثمّ حذفه من الذاكرة:

class Shape {
    public function __construct() {
        echo "Shape created!\n";
    }
}

class Rectangle extends Shape {
    public $width;
    public $height;

    public function __construct($width, $height) {
        parent::__construct();
        $this->width = $width;
        $this->height = $height;
        echo "Created {$this->width}x{$this->height} Rectangle\n";
    }

    public function __destruct() {
        echo "Destroying {$this->width}x{$this->height} Rectangle\n";
    }
}

function createRectangle() {
    // (1)
    $rectangle = new Rectangle(20, 50);
    // (2)
}

createRectangle();
// (3)

// (4)
unset(new Rectangle(20, 50));

في الموضع (1) ستستدعي تهيئة الكائن الباني مع الوسطاء المحددين.

في الموضع (2) سيُطبع 'Shape Created' ثم 'Created 20x50 Rectangle'.

في الموضع (3) سيُطبع 'Destroying 20x50 Rectangle' لأنّ الكائن ‎$rectangle هو كائن محلي بالنسبة للدالة createRectangle لذا عندما ينتهي نطاق الدالة سيُدمَّر الكائن ويُستدعى هادمه.

في الموضع (4) سيُستدعى هادم الكائن عند استخدام الدالة unset.

‎__toString()‎

يُستدعى التابع ‎__toString()‎ عندما يُعامل الكائن على أنّه سلسلة نصية، ويعيد سلسلة نصية تمثّل الصنف:

class User {
    public $first_name;
    public $last_name;
    public $age;

    public function __toString() {
        return "{$this->first_name} {$this->last_name} ($this->age)";
    }
}

$user = new User();
$user->first_name = "Chuck";
$user->last_name = "Norris";
$user->age = 76;

// في سياق سلسلة $user كلما اُستخدم الكائن‏ __toString() سيُستدعى التابع‏‏
echo $user;
// Chuck Norris (76)

// Selected user: Chuck Norris (76) :أصبحت قيمة السلسلة النصية
$selected_user_string = sprintf("Selected user: %s", $user);

// __toString() التحويل إلى سلسلة نصية يستدعي أيضًا
$user_as_string = (string) $user;

‎__clone()‎

يُستدعَى التابع ‎__clone باستخدام الكلمة المفتاحية clone، ويستخدم لمعالجة حالة كائن عند النسخ بعد أن يكون الكائن قد نُسخَ فعلًا.

class CloneableUser
{
    public $name;
    public $lastName;

    // "Copy " سيُستدعى هذا التابع بعامل النسخ ويضيف قبل خاصيّات‏‏‏ الاسم والاسم الأخير الكلمة‏
    public function __clone()
    {
        $this->name = "Copy " . $this->name;
        $this->lastName = "Copy " . $this->lastName;
    }
}

مثال:

$user1 = new CloneableUser();
$user1->name = "John";
$user1->lastName = "Doe";

//  __clone تنفيذ التابع السحري
$user2 = clone $user1; 

echo $user2->name; // Copy John
echo $user2->lastName; // Copy Doe

‎__invoke()‎

يُستدعى هذا التابع السحري عندما يحاول المستخدم استدعاء كائن كدالة، قد تتضمن حالات الاستخدام الممكنة بعض الطرق مثل البرمجة الوظيفية أو بعض ردود النداء.

سيُستدعى التابع في الشيفرة التالية إذا نُفِّذ الكائن كدالة: ‎$invokable();‎ وستمرر الوسائط كما في استدعاء التابع العادي:

class Invokable
{
    public function __invoke($arg, $arg, ...)
    {
        print_r(func_get_args());
    }
}

مثال:

$invokable = new Invokable();
$invokable([1, 2, 3]);

/*
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
*/

‎⁠__sleep()‎ و‎__wakeup()‎

يرتبط التابعان ‎__sleep و‎__wakeup بعملية السَلسَلة، تتحقق الدالة serialize إذا كان للصنف تابع ‎__sleep‎، إذا كان موجودًا سيُنفَّذ قبل أي سَلسَلة ويُفترض أن يعيد مصفوفة من أسماء كل المتغيرات للكائن الذي يجب أن يُسلسَل.

وسيُنفَّذ التابع ‎__wakeup بدوره من قبل الدالة unserialize إذا وُجد في الصنف، ويهدف إلى إعادة إنشاء الموارد والأشياء الأخرى التي نحتاج تهيئتها بعد إلغاء التسلسل.

في الشيفرة التالية سيُنفَّذ التابع السحري ‎__sleep من قِبل الدالة serialize ولاحظ أنّ ‎$dbConnection مُستبعد، وسيُنفَّذ التابع السحري ‎__wakeup من قِبل الدالة unserialize، لنفرض مثلًا أنّ ‎$this->c الذي لم يُسَلسَل هو نوع من الاتصال بقاعدة البيانات سيُعيد الاتصال عندما نستخدم تابع الاستيقاظ.

class Sleepy {
    public $tableName;
    public $tableFields;
    public $dbConnection;

    public function __sleep()
    {
        // فقط $this->tableNameو $this->tableFields سيُسَلسَل
        return ['tableName', 'tableFields'];
    }

    public function __wakeup()
    {
        // الاتصال بقاعدة البيانات الافتراضية وتخزين المُعالج/المُغلِّف فيها
        // $this->dbConnection
        $this->dbConnection = DB::connect();
    }
}

‎__debugInfo()‎

يُستدعى هذا التابع من قِبل الدالة var_dump()‎ عند تفريغ كائن للحصول على الخاصيّات التي يجب عرضها، إذا لم يُعرَّف التابع على الكائن ستُعرَض كل الخاصيّات العامة والمحمية والخاصة.

class DeepThought {
    public function __debugInfo() {
        return [42];
    }
}

الإصدار PHP ≤ 5.6

var_dump(new DeepThought());

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

class DeepThought#1 (0) {
}

الإصدار PHP ≥ 5.6

var_dump(new DeepThought());

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

class DeepThought#1 (1) {
    public ${0} =>
        int(42)
}

ترجمة -وبتصرف- للفصل [Magic Methods] من كتاب PHP Notes for Professionals book

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...