الأساليب السحرية والقوائم التعدادية

تحتوي لغة PHP على مجموعة من «الأساليب السحرية» التي تبدأ بـ __ — ولا يتم استدعاؤها أبدًا بشكل صريح؛ بل تقوم PHP بتشغيلها تلقائيًا في لحظات محددة. وهي تجعل الكائنات تتصرف بطريقة أكثر ذكاءً، تمامًا مثل الأنواع المدمجة. أضف إلى ذلك قوائم التعداد في PHP 8.1، وسترتقي هذه الدرس بمهاراتك في PHP إلى المستوى التالي.

1. __toString() — تحويل كائن إلى سلسلة

PHP
<?php
class User {
    public function __construct(
        public string $name,
        public string $email
    ) {}
    
    // Automatically called when the object is echoed
    public function __toString(): string {
        return "{$this->name} <{$this->email}>";
    }
}

$user = new User("John", "john@example.com");
echo $user;  // John <john@example.com>
// No need to write $user->name—just echo the object directly
?>
💡 نصيحة: تتيح لك __toString() تحديد «كيفية تمثيل هذا الكائن كسلسلة نصية». استخدمها في التسجيل، ومخرجات تصحيح الأخطاء، وعرض القوالب.


2. __get() و__set() — الوصول إلى الخصائص غير الموجودة

PHP
<?php
class UserModel {
    private array $data = [];
    private array $changes = [];
    
    // Called when reading a non-existent property
    public function __get(string $name): mixed {
        return $this->data[$name] ?? null;
    }
    
    // Called when assigning to a non-existent property
    public function __set(string $name, mixed $value): void {
        $this->changes[$name] = true;
        $this->data[$name] = $value;
    }
    
    public function getChanges(): array {
        return array_keys($this->changes);
    }
}

$user = new UserModel();
$user->name = "John";         // Triggers __set()
$user->email = "j@j.com";     // Triggers __set()
echo $user->name;             // John — triggers __get()
echo $user->name;             // John
print_r($user->getChanges()); // ['name', 'email'] — track which fields were modified
?>

(1) __isset() و__unset()

PHP
<?php
class Config {
    private array $data = ['app_name' => 'MyApp'];
    
    public function __isset(string $name): bool {
        return isset($this->data[$name]);
    }
    
    public function __unset(string $name): void {
        unset($this->data[$name]);
    }
}

$c = new Config();
var_dump(isset($c->app_name));  // true
var_dump(isset($c->missing));   // false
unset($c->app_name);
var_dump(isset($c->app_name));  // false
?>

3. __call() — استدعاء طرق غير موجودة

▶ مثال: أداة إنشاء الاستعلامات الديناميكية

PHP
<?php
class QueryBuilder {
    private array $where = [];
    private ?string $orderBy = null;
    
    // Triggered when calling a non-existent method
    public function __call(string $name, array $arguments): static {
        if (str_starts_with($name, 'whereBy')) {
            // whereByName("John") → WHERE name = "John"
            $field = lcfirst(substr($name, 7));
            $this->where[] = "{$field} = '{$arguments[0]}'";
        } elseif ($name === 'orderByDesc') {
            $this->orderBy = "{$arguments[0]} DESC";
        }
        return $this;
    }
    
    public function toSQL(): string {
        $sql = "SELECT * FROM table";
        if ($this->where) {
            $sql .= " WHERE " . implode(" AND ", $this->where);
        }
        if ($this->orderBy) {
            $sql .= " ORDER BY {$this->orderBy}";
        }
        return $sql;
    }
}

$q = new QueryBuilder();
$q->whereByName("John")
  ->whereByStatus("active")
  ->orderByDesc("created_at");

echo $q->toSQL();
// SELECT * FROM table WHERE name = 'John' AND status = 'active' ORDER BY created_at DESC
?>
▶ جرّب الكود
💡 نصيحة: تستخدم أطر العمل مثل Eloquent في Laravel __call() بكثرة لتنفيذ الطرق الديناميكية. فالطرق مثل whereByName() التي تبدو وكأنها موجودة ولكنها في الواقع غير مُعرَّفة، تعمل بفضل هذه الميزة.


4. __clone() — عند استنساخ كائن

PHP
<?php
class ShoppingCart {
    private array $items = [];
    
    public function __construct(
        public string $owner
    ) {}
    
    public function addItem(string $name, int $qty): void {
        $this->items[] = compact('name', 'qty');
    }
    
    public function getItems(): array {
        return $this->items;
    }
    
    // Automatically called when cloned
    public function __clone(): void {
        $this->owner = "Copy of {$this->owner}";
        // The items array is shallow-copied by default, but you can do deep copies here
    }
}

$cart1 = new ShoppingCart("John");
$cart1->addItem("PHP Tutorial", 2);

$cart2 = clone $cart1;
echo $cart2->owner;  // Copy of John
// items are also copied
print_r($cart2->getItems()); // Has the PHP Tutorial item
?>

5. __sleep() و__wakeup() — التسلسل

PHP
<?php
class DatabaseConnection {
    private $connection;
    
    public function __construct(
        private string $dsn,
        private string $user,
        private string $pass
    ) {
        $this->connect();
    }
    
    private function connect(): void {
        $this->connection = "connected({$this->dsn})";
    }
    
    // Called on serialize(): return which property names to serialize
    public function __sleep(): array {
        // Don't serialize $connection (resource types can't be serialized)
        return ['dsn', 'user', 'pass'];
    }
    
    // Called on unserialize(): re-establish the connection
    public function __wakeup(): void {
        $this->connect();
    }
}
?>

6. القوائم (PHP 8.1)

كتابة "pending" "approved" "rejected" في الدوال أمر معرض للأخطاء ويصعب تتبعه. قائمة التعدادات هي أفضل صديق لك:

PHP
<?php
enum OrderStatus: string {
    case PENDING   = 'pending';
    case APPROVED  = 'approved';
    case SHIPPED   = 'shipped';
    case DELIVERED = 'delivered';
    case CANCELLED = 'cancelled';
}

// Using enums
function updateOrder(int $id, OrderStatus $status): void {
    echo "Order {$id} updated to {$status->value}<br>";
}

updateOrder(1, OrderStatus::APPROVED);  // ✅
// updateOrder(1, "approved");          // ❌ TypeError!
// updateOrder(1, "approvved");         // ❌ Hard to even typo this
?>

▶ مثال: القوائم التعدادية + match — الثنائي القوي

PHP
<?php
enum UserRole: string {
    case ADMIN  = 'admin';
    case EDITOR = 'editor';
    case MEMBER = 'member';
    case GUEST  = 'guest';
    
    // Enum methods
    public function label(): string {
        return match($this) {
            self::ADMIN  => 'Administrator',
            self::EDITOR => 'Editor',
            self::MEMBER => 'Member',
            self::GUEST  => 'Guest',
        };
    }
    
    public function canEdit(): bool {
        return match($this) {
            self::ADMIN, self::EDITOR => true,
            default => false,
        };
    }
}

$role = UserRole::ADMIN;
echo $role->label();  // Administrator
echo $role->canEdit() ? 'Can edit' : 'Cannot edit';  // Can edit
echo $role->value;    // admin
?>
▶ جرّب الكود

(1) طرق التعداد

PHP
<?php
// UserRole::cases() — get all enum values
print_r(array_map(fn($r) => $r->value, UserRole::cases()));
// ['admin', 'editor', 'member', 'guest']

// UserRole::from() — create enum from value (throws exception on mismatch)
$role = UserRole::from('admin');   // UserRole::ADMIN
// $role = UserRole::from('super'); // ValueError

// UserRole::tryFrom() — safe creation (returns null on mismatch)
$role = UserRole::tryFrom('super'); // null
?>
💡 نصيحة: PHP 8.1 وما فوق: استخدم القوائم (enums) كلما كانت بياناتك تحتوي على مجموعة ثابتة من القيم المحتملة. OrderStatus::class يدعم ميزة الإكمال التلقائي في بيئة تطوير المتكاملة (IDE) — بينما "pending" لا يدعمها. هذا هو الفرق.

❓ أسئلة شائعة

س هل يؤثر __get و__set سلبًا على الأداء؟
ج هناك عبء بسيط نظرًا لأن كل عملية وصول تمر عبر استدعاء دالة. بالنسبة لعمليات البيانات ذات الحجم الكبير (ملايين السجلات)، تكون الخصائص الصريحة العامة/الخاصة أسرع. أما بالنسبة للسيناريوهات المرنة مثل ORMs/Models، فإن __get/__set هو الخيار الأمثل.
س متى ينبغي عليّ استخدام القوائم (enums) مقابل ثوابت الفئة؟
ج في PHP 8.1 وما بعده: يُفضل استخدام القوائم — فهي توفر أمان الأنواع (حيث يؤدي تمرير قيمة خاطئة إلى ظهور خطأ)، والإكمال التلقائي، والتحقق من الشمولية الذي يتوافق مع ميزة «match». أما ثوابت الفئة فهي الأنسب لـ«مجرد مجموعة من قيم التكوين ذات الصلة».
س هل يمكن لفئات التعداد (enum) تعريف طرق؟
ج بالطبع! الأمثلة label() وcanEdit() المذكورة أعلاه تثبت ذلك. تعمل فئات التعداد على الارتقاء برمزك البرمجي من «تمرير سلاسل نصية» إلى «تمرير أنواع ذات سلوك».

📖 ملخص

📝 تمارين

  1. اكتب فئة DynamicConfig تستخدم __get/__set/__isset/__unset لتتيح لك «التعامل مع مصفوفة التكوين كما لو كانت تحتوي على خصائص».
  2. اكتب قائمة OrderStatus (معلقة→مدفوعة→مشحونة→مُسلَّمة→ملغاة). وافرد لكل حالة طريقة canTransitionTo(OrderStatus $target) تحدد الانتقالات المسموح بها بين الحالات.
  3. اكتب فئة ORM بسيطة تستخدم __call() لتنفيذ طرق الاستعلام الديناميكية مثل whereByFieldName($value).
Web-Tutorial.com

فريق Web-Tutorial التقني

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

100%