الأساليب السحرية والقوائم التعدادية
تحتوي لغة 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() المذكورة أعلاه تثبت ذلك. تعمل فئات التعداد على الارتقاء برمزك البرمجي من «تمرير سلاسل نصية» إلى «تمرير أنواع ذات سلوك».📖 ملخص
__toString()يحدد كيفية تحويل كائن ما إلى سلسلة__get()__set()اعتراض الوصول إلى الخصائص غير الموجودة__call()يلتقط المكالمات الموجهة إلى طرق ديناميكية غير موجودة- يتم تشغيل
__clone()عند تشغيلclone $obj، مما يتيح لك إجراء نسخ عميقة - تسلسل التحكم في
__sleep()/__wakeup() - توفر قوائم التعداد في PHP 8.1
enum X: string { case A = 'a'; }أمانًا في الأنواع cases()/from()/tryFrom()هي طرق أساسية في قائمة التعداد- Enums +
match{}هما الثنائي القوي
📝 تمارين
- اكتب فئة
DynamicConfigتستخدم__get/__set/__isset/__unsetلتتيح لك «التعامل مع مصفوفة التكوين كما لو كانت تحتوي على خصائص». - اكتب قائمة
OrderStatus(معلقة→مدفوعة→مشحونة→مُسلَّمة→ملغاة). وافرد لكل حالة طريقةcanTransitionTo(OrderStatus $target)تحدد الانتقالات المسموح بها بين الحالات. - اكتب فئة ORM بسيطة تستخدم
__call()لتنفيذ طرق الاستعلام الديناميكية مثلwhereByFieldName($value).



