انتقل إلى محتوى الدرس
الدرس 71
دورة PHP

الدوال السحرية ونسخ الكائنات بـ clone ومقارنتها في PHPPHP Magic Methods Object Cloning and Object Comparison

خصّص سلوك الكائنات في PHP 8 عبر الدوال السحرية __toString و__invoke و__clone، وافهم النسخ السطحي والعميق ومقارنة الكائنات.
أقسام الدرس
6
الأمثلة
6
اللغة
العربية
تاريخ النشر
غير محدد
المشاهدات
0
محتوى الدرس
في هذا الدرس 6تصفّح المواضيع

تحدد الدالة السحرية __toString(): string كيفية تحويل الكائن إلى نص تلقائياً عند تمريره إلى echo أو دمجه داخل نص مزدوج أو تحويله بـ (string).

  • تبدأ جميع الدوال السحرية في PHP بشرطتين سفليتين (__) ويستدعيها المحرك تلقائياً عند حدوث عمليات معينة على الكائن.
  • لا تأخذ الدالة __toString() أي معاملات، ويجب إلزاماً أن تُرجع قيمة نصية من النوع string.
  • في PHP 8.0+، أي صنف يعرّف الدالة __toString() يطبّق ضمنياً الواجهة المدمجة Stringable.

إن وضع الكائن {$price} داخل علامتي تنصيص مزدوجتين يستدعي تلقائياً الدالة $price->__toString().

مخطط توضيحي: محاكي الدوال السحرية ونسخ الكائنات بـ clone ومقارنتها في PHP
PHP
<?php
class Money {
    public function __construct(private int $cents, private string $curr) {}
    public function __toString(): string {
        return number_format($this->cents / 100, 2) . " " . $this->curr;
    }
}
$price = new Money(14995, "SAR");
echo "Total Due: {$price}\n";

يتيح تعريف الدالة السحرية __invoke() استدعاء الكائن مباشرة بالأقواس $object($arg) وكأنه دالة برمجية.

  • أي كائن يحتوي على __invoke() يحقق شرط النوع callable ويمكن تمريره إلى دوال مثل array_map() وarray_filter().
  • على عكس الدالة العادية، يمكن للكائن القابل للاستدعاء الاحتفاظ بإعدادات مسبقة عبر دالة البناء مع العمل كصنف ذي مهمة واحدة.
  • تُستخدم __invoke() بكثرة في المتحكمات أحادية المهمة (Single-Action Controllers) والطبقات الوسيطة (Middleware).

تم تمرير الكائن new MinPriceFilter(100) مباشرة كدالة رد نداء إلى array_filter() التي تستدعي __invoke($price) لكل عنصر.

PHP
<?php
class MinPriceFilter {
    public function __construct(private int $min) {}
    public function __invoke(int $price): bool {
        return $price >= $this->min;
    }
}
$prices = [40, 120, 85, 210];
$kept = array_values(array_filter($prices, new MinPriceFilter(100)));
echo implode(", ", $kept) . "\n";

يستدعي PHP الدالة __get($name) عند محاولة قراءة خاصية غير موجودة أو غير متاحة، ويستدعي __call($name, $args) عند استدعاء دالة غير معرفة في الكائن.

  • ترتبط بـ __get دوال مكملة مثل __set($name, $value) عند الكتابة و__isset($name) عند الفحص بـ isset().
  • في PHP 8.2+، أصبحت إضافة خصائص ديناميكية غير معلنة للكائنات العادية مهملة (Deprecated)؛ لذا استخدم مصفوفة داخلية خاصة مع __get() عند الحاجة.
  • فضّل دائماً الخصائص والدوال الصريحة ذات الأنواع المحددة عندما تكون الهيكلة معروفة مسبقاً لدعم الإكمال التلقائي في المحررات.

قراءة $cfg->env تستدعي __get("env")، واستدعاء $cfg->refresh("cache") يمرر الاسم والمعاملات إلى __call().

PHP
<?php
class ConfigBag {
    public function __construct(private array $items) {}
    public function __get(string $key): mixed {
        return $this->items[$key] ?? "default";
    }
    public function __call(string $method, array $args): string {
        return "Handled {$method}(" . implode(",", $args) . ")";
    }
}
$cfg = new ConfigBag(["env" => "production"]);
echo $cfg->env . " | " . $cfg->refresh("cache") . "\n";

إن إسناد الكائن عبر $b = $a لا ينشئ كائناً جديداً، بل يشير المتغيران إلى نفس الكائن في الذاكرة. استخدم الكلمة clone لإنشاء نسخة مستقلة.

  • افتراضياً، ينفذ clone $obj نسخاً سطحياً (Shallow Copy): تُنسخ القيم المفردة والمصفوفات، لكن الخصائص التي تحمل كائنات فرعية تظل تشير لنفس الكائنات الفرعية.
  • عقب إنشاء النسخة السطحية، يستدعي PHP تلقائياً الدالة __clone() داخل النسخة الجديدة (إن وُجدت) دون إعادة تشغيل __construct().
  • داخل __clone()، انسخ أي كائنات فرعية صراحةً ($this->child = clone $this->child;) لتحقيق نسخ عميق (Deep Copy) مستقل تماماً.

لأن الدالة Customer::__clone() تنسخ الكائن الفرعي $this->addr، فإن تعديل مدينة $c2 لا يؤثر إطلاقاً على مدينة $c1.

مخطط تدفق التنفيذ العملي لقسم نسخ الكائنات بالكلمة clone والنسخ العميق في __clone في لغة PHP
PHP
<?php
class Address { public function __construct(public string $city) {} }
class Customer {
    public function __construct(public string $name, public Address $addr) {}
    public function __clone(): void {
        $this->addr = clone $this->addr;
    }
}
$c1 = new Customer("Sara", new Address("Riyadh"));
$c2 = clone $c1;
$c2->addr->city = "Jeddah";
echo "c1={$c1->addr->city}, c2={$c2->addr->city}\n";

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

  • فور تنفيذ $copy = clone $original، تكون نتيجة $copy == $original هي true (نفس الصنف والقيم)، بينما نتيجة $copy === $original هي false (كائنان منفصلان).
  • بعد الإسناد العادي $alias = $original، تكون نتيجة كل من $alias == $original و$alias === $original هي true.
  • أي كائنين من صنفين مختلفين لا يتساويان أبداً سواء بـ == أو بـ ===.

المتغير $b2 نسخة مستنسخة (نفس القيم بـ == ولكن هوية مستقلة بـ !==)، بينما $b3 يشير لنفس الكائن $b1 في الذاكرة.

PHP
<?php
class Badge { public function __construct(public string $code) {} }

$b1 = new Badge("GOLD");
$b2 = clone $b1;
$b3 = $b1;

echo "b1 == b2  : " . ($b1 == $b2 ? "true" : "false") . "\n";
echo "b1 === b2 : " . ($b1 === $b2 ? "true" : "false") . "\n";
echo "b1 === b3 : " . ($b1 === $b3 ? "true" : "false") . "\n";

استخدم هذا الجدول المرجعي لمعرفة متى يستدعي محرك PHP كل دالة سحرية خلال دورة حياة الكائن.

  • يجب إعلان جميع الدوال السحرية برؤية public (باستثناء __construct و__destruct و__clone التي يمكن تقييدها في أنماط معينة).
  • تتيح الدالة __debugInfo(): array إخفاء الحقول الحساسة (مثل مفاتيح API أو كلمات المرور) عند طباعة الكائن بـ var_dump($obj).
  • تجنب الإفراط في __get() و__set() عندما تكون الخصائص الصريحة أو خطافات PHP 8.4 كافية.

يضمن تعريف الدالة __debugInfo() حجب المفتاح السري $secret بحيث لا تتسرب البيانات الحساسة أثناء الفحص.

الدالة السحريةتُستدعى تلقائياً عندنوع القيمة المعادة
__construct(...)Creating a new instance with newNone (void disallowed)
__toString(): stringCasting object to string or echo $objstring
__invoke(...)Calling object as a function $obj()mixed
__get($name) / __call($m, $a)Accessing inaccessible property / methodmixed
__clone(): voidAfter shallow copy via clone $objvoid

PHP
<?php
class ApiClient {
    public function __construct(public string $host, private string $secret) {}
    public function __debugInfo(): array {
        return ["host" => $this->host, "secret" => "***REDACTED***"];
    }
}
$client = new ApiClient("api.example.com", "sk_live_998877");
echo json_encode($client->__debugInfo()) . "\n";
خلاصة المعرفة ومحطة الانتقال

ملخص الدرس

حصاد الدرس

مراجعة مركّزة لأهم المفاهيم والقواعد الهندسية التي أتقنتها في هذا الدرس قبل التطبيق العملي

ماذا تعلّمت في هذا الدرس؟

استخدم هذا الجدول المرجعي لمعرفة متى يستدعي محرك PHP كل دالة سحرية خلال دورة حياة الكائن. يجب إعلان جميع الدوال السحرية برؤية public (باستثناء __construct و__destruct و__clone التي يمكن تقييدها في أنماط معينة). تتيح الدالة __debugInfo(): array إخفاء الحقول الحساسة (مثل مفاتيح API أو كلمات المرور) عند طباعة الكائن بـ var_dump($obj). تجنب الإفراط في __get() و__set() عندما تكون الخصائص الصريحة أو خطافات PHP 8.4 كافية.

مشاركة

هل كان هذا الدرس مفيدًا؟

جارٍ تحميل الآراء…

ابنِ لغتك البرمجية

المصطلحات4

راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.

0 / 4 مفهومةأول بطاقة، أول خطوة
01 Magic Methods & Clone الدوال السحرية و clone
خصّص سلوك الكائنات في PHP 8 عبر الدوال السحرية __toString و__invoke و__clone، وافهم النسخ السطحي والعميق ومقارنة الكائنات.
للمراجعة
02 String Representation with __toString تحويل الكائن إلى نص قابل للطباعة عبر __toString
المفهوم الأساسي لقسم تحويل الكائن إلى نص قابل للطباعة عبر __toString في لغة PHP الحديثة.
للمراجعة
03 Callable Objects with __invoke استدعاء الكائن كدالة برمجية عبر __invoke
الصياغة البرمجية والتطبيق العملي المباشر لقسم استدعاء الكائن كدالة برمجية عبر __invoke.
للمراجعة
04 Handling Dynamic Calls with __get and __call التعامل مع الخصائص والدوال الديناميكية بـ __get و __call
قواعد التحقق وأفضل الممارسات القياسية الخاصة بقسم التعامل مع الخصائص والدوال الديناميكية بـ __get و __call.
للمراجعة

اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.

حروف قليلة. معنى كامل.

الاختصارات4

راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.

0 / 4 مفهومةأول بطاقة، أول خطوة
01 PHP PHP
PHP: Hypertext Preprocessor
لغة البرمجة النصية على جانب الخادم لبناء تطبيقات الويب الديناميكية وواجهات البرمجة.
للمراجعة
02 CLI CLI
Command-Line Interface
واجهة سطر الأوامر التي تتيح تشغيل ملفات PHP مباشرة عبر الطرفية.
للمراجعة
03 FPM FPM
FastCGI Process Manager
مدير عمليات FastCGI عالي الأداء لمعالجة طلبات خادم الويب في PHP.
للمراجعة
04 HTML HTML
HyperText Markup Language
لغة الترميز القياسية التي يولدها محرك PHP ويرسلها إلى متصفح العميل بعد التنفيذ.
للمراجعة

اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.

من الفهم للتطبيق

مهامك قبل الاختبار

خطوة صغيرة كل مرة. وإنجاز تقدر ترجع له.

0/2مهام منجزة
  1. أنشئ ملف PHP محلياً ونفّذ المثال البرمجي المشروح في قسم تحويل الكائن إلى نص قابل للطباعة عبر __toString للتحقق من المخرجات.

  2. بدّل بين الحالات الثلاث في المحاكي التفاعلي ضمن قسم استدعاء الكائن كدالة برمجية عبر __invoke وقارن مخرجات التنفيذ.

تأشير المهمة إقرار منك بإتمامها، وليس تصحيحًا آليًا للكود. يُحفظ تقدمك في حسابك.

مساحة التحدي

اختبر فهمك.
ووصلها للإتقان.

كل سؤال خطوة لقدّام. اختار، اكتب كود، وشوف نتيجتك فورًا.

8أسئلة 1أنواع تحديات 100%هدف الإتقان
تصحيح تلقائي. فرص بلا حدود.

أكمل المهام، واجمع كل الإجابات الصحيحة. أقل من 100%؟ راجع وأعد المحاولة.

الأسئلة متاحة للاطلاع. سجّل الدخول للتفاعل وحفظ تقدمك.

لمحة عن التحدي8 أسئلة تنتظرك
01

ما الدالة السحرية التي يتم استدعاؤها تلقائياً عند تمرير كائن إلى echo أو دمجه داخل نص مزدوج؟

تُرجع __toString() التمثيل النصي للكائن.

02

ماذا يتيح لك تعريف الدالة السحرية __invoke() داخل الصنف عند التعامل مع الكائن $obj؟

تجعل __invoke() الكائن قابلاً للاستدعاء المباشر بالأقواس.

03

متى يستدعي PHP الدالة السحرية __get(string $name) على الكائن؟

الخصائص العامة تُقرأ مباشرة، ولا تعمل __get() إلا مع الخصائص غير الموجودة أو المخفية.

04

ماذا يحدث عند تنفيذ $b = $a; عندما يحتوي المتغير $a على كائن؟

تحمل متغيرات الكائنات معرفاً يشير لنفس الكائن في الذاكرة ما لم يتم نسخه بـ clone.

05

لماذا تحتاج إلى تعريف الدالة __clone() عندما يحتوي الصنف على خاصية تحمل كائناً فرعياً قابلاً للتعديل (مثل public Address $addr)؟

بدون نسخ الكائنات الفرعية داخل __clone()، ستشير النسختان إلى نفس الكائن الفرعي.

06

بفرض $x = new Tag("php"); و$y = clone $x;، ما نتيجة كل من ($x == $y) و($x === $y)؟

تفحص == تطابق الصنف والقيم (true)، بينما تفحص === تطابق الهوية في الذاكرة (false بعد clone).

07

ما الواجهة المدمجة في PHP 8 التي يطبقها الصنف ضمنياً بمجرد تعريفه للدالة __toString()؟

يعامل PHP 8 أي صنف يحتوي على __toString() ككائن يطبق الواجهة Stringable تلقائياً.

08

ما الدالة السحرية التي تلتقط استدعاء الدوال غير الموجودة أو الخاصة على الكائن مثل $service->missingMethod(1, 2)؟

تستقبل __call() اسم الدالة المستدعاة ومصفوفة بالمعاملات الممررة.