السمات والأعضاء الثابتة
تتمتع لغة PHP بميزتين لا تتوفران في معظم اللغات الشائعة الأخرى (أو لا يتم تصميمهما بنفس الطريقة): السمات (Traits) والربط الثابت المتأخر (late static binding). يقدم لك هذا الدرس هاتين الأداتين الفريدتين في PHP لكتابة كود أكثر أناقة.
1. السمات — سحر إعادة استخدام الكود في لغة PHP
تتميز لغة PHP بالوراثة الأحادية — أي أن الفئة لا يمكن أن يكون لها سوى فئة أصلية واحدة. لكن في بعض الأحيان، تحتاج فئات غير مرتبطة ببعضها إلى مشاركة نفس الجزء من الكود (مثل «تسجيل الطوابع الزمنية» أو «إنشاء معرّفات فريدة»). وقد تم ابتكار «السمات» (Traits) لهذا الغرض.
<?php
// Define a Trait
trait HasTimestamp {
private string $createdAt;
private string $updatedAt;
public function touch(): void {
$now = date("Y-m-d H:i:s");
if (!isset($this->createdAt)) {
$this->createdAt = $now;
}
$this->updatedAt = $now;
}
public function getCreatedAt(): string {
return $this->createdAt ?? 'Unknown';
}
public function getUpdatedAt(): string {
return $this->updatedAt ?? 'Unknown';
}
}
trait HasUuid {
public function generateUuid(): string {
return uniqid() . bin2hex(random_bytes(8));
}
}
// Use Traits in completely different classes
class User {
use HasTimestamp, HasUuid; // Use multiple Traits
public function __construct(
public string $name
) {
$this->touch();
}
}
class Article {
use HasTimestamp;
public function __construct(
public string $title,
public string $content
) {
$this->touch();
}
}
$user = new User("John");
echo $user->name . " created at " . $user->getCreatedAt() . "<br>";
echo "UUID: " . $user->generateUuid() . "<br>";
$article = new Article("PHP Traits Tutorial", "Main content...");
echo $article->title . " last updated at " . $article->getUpdatedAt() . "<br>";
?>
2. حل النزاعات المتعلقة بالسمات
عندما تحتوي سمات متعددة على طرق تحمل الاسم نفسه:
<?php
trait Logger {
public function log(string $msg): void {
echo "[LOG] {$msg}<br>";
}
}
trait FileLogger {
public function log(string $msg): void {
echo "[FILE] {$msg}<br>";
}
}
class App {
use Logger, FileLogger {
// Replace Logger's log with FileLogger's log
FileLogger::log insteadof Logger;
// Give the replaced method an alias so it's still callable
Logger::log as logSimple;
}
}
$app = new App();
$app->log("App started"); // [FILE] App started
$app->logSimple("App started"); // [LOG] App started
?>
▶ مثال: مجموعة سمات عملية
<?php
trait Arrayable {
public function toArray(): array {
return get_object_vars($this);
}
public function toJson(): string {
return json_encode($this->toArray(), JSON_UNESCAPED_UNICODE);
}
}
trait Validatable {
public function validate(array $rules): array {
$errors = [];
foreach ($rules as $field => $rule) {
$value = $this->$field ?? null;
if (str_contains($rule, 'required') && empty($value)) {
$errors[$field] = "{$field} is required";
}
}
return $errors;
}
}
class Product {
use Arrayable, Validatable;
public function __construct(
public string $name,
public float $price,
public string $description = '',
) {}
}
$p = new Product("Mechanical Keyboard", 299);
echo $p->toJson() . "<br>";
print_r($p->validate(['name' => 'required', 'price' => 'required']));
?>
3. الخصائص والطرق الثابتة
تنتمي العناصر الثابتة إلى الفئة نفسها، وليس إلى المثيلات. وتشترك جميع الكائنات في نفس الخاصية الثابتة:
<?php
class Counter {
private static int $count = 0;
public function __construct() {
self::$count++; // self:: accesses static members
}
public static function getCount(): int {
return self::$count;
}
}
echo Counter::getCount(); // 0 (call with ClassName::method)
$a = new Counter();
$b = new Counter();
$c = new Counter();
echo Counter::getCount(); // 3 (all instances share a single $count)
?>
خاصية المثيل $this-> |
خاصية ثابتة self::$ |
|
|---|---|---|
| ينتمي إلى | كل كائن | الفئة نفسها |
| الذاكرة | نسخة واحدة لكل كائن | نسخة عالمية واحدة |
| الوصول | $obj->prop |
Class::$prop |
4. self:: مقابل static:: (الربط الثابت المتأخر)
هذا أحد أكثر مفاهيم لغة PHP دقةً — وأكثرها إثارةً للالتباس —:
<?php
class ParentClass {
public static function who(): string {
return 'Parent';
}
public static function test(): string {
return self::who(); // self:: always points to the defining class (compile-time binding)
}
public static function testLate(): string {
return static::who(); // static:: points to the actual calling class (runtime binding)
}
}
class ChildClass extends ParentClass {
public static function who(): string {
return 'Child';
}
}
echo ChildClass::test(); // "Parent" ← self:: bound to ParentClass
echo ChildClass::testLate(); // "Child" ← static:: bound to ChildClass
?>
self:: |
static:: |
|
|---|---|---|
| وقت الربط | وقت الترجمة | وقت التشغيل |
| تشير إلى | الفئة التي تم تعريف الطريقة فيها | الفئة الفعلية التي تستدعي الطريقة |
| الأنسب لـ | الطرق المساعدة، الثوابت | الطرق الثابتة التي يجب على الفئات الفرعية تجاوزها |
▶ مثال: الربط الثابت المتأخر في الممارسة العملية — نموذج ORM بسيط
<?php
abstract class Model {
// static:: lets subclasses return their own table name
public static function table(): string {
// Default: class name → snake_case → plural
return strtolower(static::class) . 's';
}
public static function find(int $id): string {
return "SELECT * FROM " . static::table() . " WHERE id = {$id}";
}
}
class User extends Model {}
class Product extends Model {
public static function table(): string {
return 'products'; // Custom table name
}
}
echo User::find(1); // SELECT * FROM users WHERE id = 1
echo Product::find(5); // SELECT * FROM products WHERE id = 5
// static::table() returns each subclass's own table name
?>
5. الثوابت في الفئات (const)
ثوابت الفئة لا تتغير من كائن لآخر — وهي مثالية لقيم التكوين والاتفاقيات:
<?php
class HttpStatus {
public const OK = 200;
public const NOT_FOUND = 404;
public const INTERNAL_ERROR = 500;
public static function getMessage(int $code): string {
return match($code) {
self::OK => 'OK',
self::NOT_FOUND => 'Not Found',
self::INTERNAL_ERROR => 'Internal Server Error',
default => 'Unknown',
};
}
}
echo HttpStatus::OK; // 200
echo HttpStatus::NOT_FOUND; // 404
echo HttpStatus::getMessage(404); // Not Found
// PHP 8.1+ final const
class Config {
final public const APP_NAME = 'MyBlog';
// Subclasses can't override final constants
}
?>
define() — فهي تتمتع بمساحات أسماء، وتدعم الإكمال التلقائي، ولا تلوث النطاق العام. إذا كانت مجموعة من القيم تنتمي إلى نفس المفهوم (رموز حالة HTTP، أدوار المستخدمين)، فقم بتنظيمها باستخدام الثوابت الخاصة بالفئات.
❓ أسئلة شائعة
use عدة سمات، لكنها لا يمكنها أن تمتد إلا لفئة مجردة واحدة.self:: وstatic:: مهمًا فعليًّا؟self:: كافية.📖 ملخص
- تعمل السمات على التغلب على قيود الوراثة الأحادية، مما يتيح للفئات غير المرتبطة ببعضها مشاركة الكود
use Trait1, Trait2 { ... }يحل التضارب معinsteadof+asself::$propيصل إلى الخصائص/الطرق الثابتة (التي تنتمي إلى الفئة، وتشترك فيها جميع المثيلات)self::يتم ربطه في وقت التحويل البرمجي (يشير إلى مكان تعريفه)،static::يتم ربطه في وقت التشغيل (يشير إلى المُستدعي)- الثوابت الخاصة بالفئة
public const KEY = valueأفضل من الثوابت العالميةdefine()
📝 تمارين
- اكتب سمة
Loggableمع طريقةlog($message)(تقوم تلقائيًا بإضافة طابع زمني كبادئة). أنشئ فئتين غير مرتبطتين،UserوProduct، ثم قم بتطبيقuseعليهما. - اكتب فئة أصلية
Database، واستخدمstatic::لتنفيذ طريقةtable()، بحيث تُرجع الفئات الفرعيةUser/Orderusers/ordersعلى التوالي. اكتب طريقة ثابتةall()تُرجعSELECT * FROM [table]. - أنشئ فئة
AppConfig، واستخدم الثوابت الخاصة بالفئة لتعريف APP_NAME وMAX_UPLOAD_SIZE وDEFAULT_LANGUAGE. اكتب دالة ثابتةdisplay()تعرض جميع قيم التكوين.



