أساسيات PHP

الأعضاء الثابتة والثوابت

13 دقيقة الدرس 24 من 45

فهم الأعضاء الثابتة

الخصائص والدوال الثابتة تنتمي إلى الصنف نفسه، وليس إلى كائنات فردية. يمكن الوصول إليها دون إنشاء مثيل من الصنف.

المفاهيم الأساسية:
  • الخاصية الثابتة: متغير مشترك بين جميع مثيلات الصنف
  • الدالة الثابتة: وظيفة يمكن استدعاؤها دون إنشاء كائن
  • الوصول: استخدم عامل :: (عامل دقة النطاق)
  • الكلمة المفتاحية self: تشير إلى الصنف الحالي في السياق الثابت

الخصائص الثابتة

الخصائص الثابتة مشتركة عبر جميع مثيلات الصنف:

<?php class Counter { public static $count = 0; // خاصية ثابتة private $id; public function __construct() { self::$count++; // زيادة الخاصية الثابتة $this->id = self::$count; } public function getId() { return $this->id; } public static function getCount() { return self::$count; } } // الوصول إلى الخاصية الثابتة دون إنشاء كائن echo Counter::$count; // النتيجة: 0 // إنشاء كائنات $obj1 = new Counter(); $obj2 = new Counter(); $obj3 = new Counter(); // التحقق من الخاصية الثابتة echo Counter::$count; // النتيجة: 3 echo Counter::getCount(); // النتيجة: 3 echo $obj1->getId(); // النتيجة: 1 echo $obj2->getId(); // النتيجة: 2 echo $obj3->getId(); // النتيجة: 3 ?>
مهم: استخدم self::$property للوصول إلى الخصائص الثابتة داخل الصنف، و ClassName::$property خارج الصنف.

الدوال الثابتة

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

<?php class MathHelper { // دوال ثابتة للعمليات الرياضية public static function add($a, $b) { return $a + $b; } public static function multiply($a, $b) { return $a * $b; } public static function power($base, $exponent) { return pow($base, $exponent); } public static function average(...$numbers) { if (empty($numbers)) { return 0; } return array_sum($numbers) / count($numbers); } } // استدعاء الدوال الثابتة دون إنشاء كائن echo MathHelper::add(5, 3); // النتيجة: 8 echo MathHelper::multiply(4, 7); // النتيجة: 28 echo MathHelper::power(2, 10); // النتيجة: 1024 echo MathHelper::average(10, 20, 30); // النتيجة: 20 ?>

مثال عملي: اتصال قاعدة البيانات

تُستخدم الأعضاء الثابتة عادة لأنماط الوحدة الواحدة والموارد المشتركة:

<?php class Database { private static $instance = null; private static $connectionCount = 0; private $connection; // البناء الخاص يمنع الإنشاء المباشر private function __construct() { self::$connectionCount++; echo "اتصال قاعدة البيانات #{self::$connectionCount} تم إنشاؤه\n"; // محاكاة اتصال قاعدة البيانات $this->connection = "اتصال MySQL"; } // الحصول على مثيل واحد (نمط الوحدة الواحدة) public static function getInstance() { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } public static function getConnectionCount() { return self::$connectionCount; } public function query($sql) { return "تنفيذ: {$sql} على {$this->connection}"; } // منع الاستنساخ private function __clone() {} // منع إلغاء التسلسل private function __wakeup() {} } // الحصول على مثيل قاعدة البيانات $db1 = Database::getInstance(); // ينشئ الاتصال #1 $db2 = Database::getInstance(); // يرجع نفس المثيل $db3 = Database::getInstance(); // يرجع نفس المثيل echo Database::getConnectionCount(); // النتيجة: 1 (اتصال واحد فقط تم إنشاؤه) echo $db1->query("SELECT * FROM users"); // جميع المتغيرات تشير إلى نفس المثيل var_dump($db1 === $db2); // true var_dump($db2 === $db3); // true ?>
نمط الوحدة الواحدة: يضمن أن يكون للصنف مثيل واحد فقط ويوفر نقطة وصول عامة إليه. مفيد لاتصالات قواعد البيانات ومديري التكوين والمسجلات.

مثال واقعي: مصادقة المستخدم

لننشئ نظام مصادقة شامل باستخدام الأعضاء الثابتة:

<?php class Auth { private static $currentUser = null; private static $loginAttempts = []; private static $maxAttempts = 5; private static $lockoutTime = 900; // 15 دقيقة بالثواني // التحقق من تسجيل دخول المستخدم public static function isLoggedIn() { return self::$currentUser !== null; } // الحصول على المستخدم الحالي public static function getCurrentUser() { return self::$currentUser; } // دالة تسجيل الدخول public static function login($username, $password) { // التحقق من قفل IP $ip = self::getClientIP(); if (self::isLockedOut($ip)) { return [ "success" => false, "message" => "محاولات فاشلة كثيرة جداً. حاول مرة أخرى لاحقاً." ]; } // التحقق من الاعتمادات (مبسط) if (self::validateCredentials($username, $password)) { self::$currentUser = [ "username" => $username, "login_time" => time(), "ip" => $ip ]; self::clearAttempts($ip); return [ "success" => true, "message" => "تسجيل دخول ناجح" ]; } // تسجيل محاولة فاشلة self::recordAttempt($ip); return [ "success" => false, "message" => "اعتمادات غير صالحة" ]; } // دالة تسجيل الخروج public static function logout() { self::$currentUser = null; } // طلب المصادقة public static function requireAuth() { if (!self::isLoggedIn()) { throw new Exception("المصادقة مطلوبة"); } } // التحقق من انتهاء صلاحية الجلسة public static function isSessionExpired($maxAge = 3600) { if (!self::isLoggedIn()) { return true; } return (time() - self::$currentUser["login_time"]) > $maxAge; } // دوال مساعدة خاصة private static function validateCredentials($username, $password) { // التحقق المبسط $validUsers = [ "admin" => "admin123", "user" => "password123" ]; return isset($validUsers[$username]) && $validUsers[$username] === $password; } private static function getClientIP() { return $_SERVER["REMOTE_ADDR"] ?? "127.0.0.1"; } private static function isLockedOut($ip) { if (!isset(self::$loginAttempts[$ip])) { return false; } $attempts = self::$loginAttempts[$ip]; if (count($attempts) < self::$maxAttempts) { return false; } $lastAttempt = end($attempts); return (time() - $lastAttempt) < self::$lockoutTime; } private static function recordAttempt($ip) { if (!isset(self::$loginAttempts[$ip])) { self::$loginAttempts[$ip] = []; } self::$loginAttempts[$ip][] = time(); } private static function clearAttempts($ip) { if (isset(self::$loginAttempts[$ip])) { unset(self::$loginAttempts[$ip]); } } public static function getAttemptCount($ip) { return isset(self::$loginAttempts[$ip]) ? count(self::$loginAttempts[$ip]) : 0; } } // مثال الاستخدام if (!Auth::isLoggedIn()) { $result = Auth::login("admin", "admin123"); if ($result["success"]) { echo "مرحباً، " . Auth::getCurrentUser()["username"]; } else { echo "فشل تسجيل الدخول: " . $result["message"]; } } // حماية صفحة try { Auth::requireAuth(); echo "هذه صفحة محمية"; } catch (Exception $e) { echo "الوصول مرفوض: " . $e->getMessage(); } // التحقق من انتهاء صلاحية الجلسة if (Auth::isSessionExpired(1800)) { // 30 دقيقة Auth::logout(); echo "انتهت صلاحية الجلسة، يرجى تسجيل الدخول مرة أخرى"; } ?>

ثوابت الصنف

الثوابت هي قيم لا يمكن تغييرها بمجرد تعريفها:

<?php class Configuration { // تعريف الثوابت public const APP_NAME = "تطبيقي"; public const VERSION = "1.0.0"; public const MAX_UPLOAD_SIZE = 5242880; // 5MB بالبايت private const API_SECRET = "مفتاح-سري-123"; // يمكن استخدام الثوابت في الحسابات public const UPLOAD_SIZE_MB = self::MAX_UPLOAD_SIZE / 1024 / 1024; public static function getConfig($key) { $constants = [ "app_name" => self::APP_NAME, "version" => self::VERSION, "max_upload" => self::MAX_UPLOAD_SIZE ]; return $constants[$key] ?? null; } public static function validateUploadSize($fileSize) { return $fileSize <= self::MAX_UPLOAD_SIZE; } // ثابت خاص يمكن الوصول إليه فقط داخل الصنف public static function getApiUrl() { return "https://api.example.com/" . self::API_SECRET; } } // الوصول إلى الثوابت echo Configuration::APP_NAME; // النتيجة: تطبيقي echo Configuration::VERSION; // النتيجة: 1.0.0 echo Configuration::UPLOAD_SIZE_MB; // النتيجة: 5 // الاستخدام في الدوال $fileSize = 6000000; // 6MB if (Configuration::validateUploadSize($fileSize)) { echo "حجم الملف مقبول"; } else { echo "الملف كبير جداً. الحد الأقصى: " . Configuration::UPLOAD_SIZE_MB . "MB"; } ?>
الثوابت مقابل الخصائص الثابتة:
  • الثوابت: لا يمكن تغييرها، استخدم الكلمة المفتاحية const
  • الخصائص الثابتة: يمكن تعديلها، استخدم الكلمة المفتاحية static

التعدادات مع الثوابت

إنشاء هياكل تشبه التعداد باستخدام الثوابت:

<?php class OrderStatus { public const PENDING = "pending"; public const PROCESSING = "processing"; public const SHIPPED = "shipped"; public const DELIVERED = "delivered"; public const CANCELLED = "cancelled"; public static function isValid($status) { $validStatuses = [ self::PENDING, self::PROCESSING, self::SHIPPED, self::DELIVERED, self::CANCELLED ]; return in_array($status, $validStatuses); } public static function getAll() { return [ self::PENDING, self::PROCESSING, self::SHIPPED, self::DELIVERED, self::CANCELLED ]; } public static function getLabel($status) { $labels = [ self::PENDING => "قيد الانتظار", self::PROCESSING => "قيد المعالجة", self::SHIPPED => "تم الشحن", self::DELIVERED => "تم التسليم", self::CANCELLED => "ملغي" ]; return $labels[$status] ?? "غير معروف"; } } class Order { private $id; private $status; private $items; public function __construct($id) { $this->id = $id; $this->status = OrderStatus::PENDING; $this->items = []; } public function setStatus($status) { if (OrderStatus::isValid($status)) { $this->status = $status; return true; } return false; } public function getStatus() { return $this->status; } public function getStatusLabel() { return OrderStatus::getLabel($this->status); } public function canBeCancelled() { return in_array($this->status, [ OrderStatus::PENDING, OrderStatus::PROCESSING ]); } } // الاستخدام $order = new Order(12345); echo $order->getStatusLabel(); // النتيجة: قيد الانتظار $order->setStatus(OrderStatus::PROCESSING); echo $order->getStatusLabel(); // النتيجة: قيد المعالجة if ($order->canBeCancelled()) { $order->setStatus(OrderStatus::CANCELLED); echo "تم إلغاء الطلب"; } // سرد جميع الحالات foreach (OrderStatus::getAll() as $status) { echo OrderStatus::getLabel($status) . "\n"; } ?>

الربط الثابت المتأخر

استخدم static:: بدلاً من self:: للربط الثابت المتأخر في الوراثة:

<?php class Animal { protected static $species = "غير معروف"; public static function getSpecies() { return self::$species; // ربط مبكر - يرجع دائماً "غير معروف" } public static function getSpeciesLate() { return static::$species; // ربط متأخر - يرجع قيمة الصنف الابن } public static function create() { return new static(); // ينشئ مثيلاً من الصنف المستدعى } } class Dog extends Animal { protected static $species = "Canis familiaris"; } class Cat extends Animal { protected static $species = "Felis catus"; } // ربط مبكر (self::) echo Dog::getSpecies(); // النتيجة: غير معروف (يستخدم قيمة الأب) echo Cat::getSpecies(); // النتيجة: غير معروف (يستخدم قيمة الأب) // ربط ثابت متأخر (static::) echo Dog::getSpeciesLate(); // النتيجة: Canis familiaris echo Cat::getSpeciesLate(); // النتيجة: Felis catus // إنشاء مثيلات باستخدام الربط الثابت المتأخر $dog = Dog::create(); // ينشئ مثيل Dog $cat = Cat::create(); // ينشئ مثيل Cat echo get_class($dog); // النتيجة: Dog echo get_class($cat); // النتيجة: Cat ?>
self مقابل static:
  • self:: يشير إلى الصنف حيث كُتب (ربط مبكر)
  • static:: يشير إلى الصنف الذي تم استدعاؤه (ربط متأخر)
  • استخدم static:: عندما تريد أن تستخدم الأصناف الأبناء قيمها الخاصة

مثال عملي: نظام التسجيل

نظام تسجيل شامل باستخدام الأعضاء الثابتة:

<?php class Logger { // مستويات التسجيل كثوابت public const DEBUG = "DEBUG"; public const INFO = "INFO"; public const WARNING = "WARNING"; public const ERROR = "ERROR"; public const CRITICAL = "CRITICAL"; private static $logs = []; private static $logLevel = self::INFO; private static $logToFile = false; private static $logFile = "app.log"; public static function setLogLevel($level) { self::$logLevel = $level; } public static function enableFileLogging($filename = "app.log") { self::$logToFile = true; self::$logFile = $filename; } public static function debug($message, $context = []) { self::log(self::DEBUG, $message, $context); } public static function info($message, $context = []) { self::log(self::INFO, $message, $context); } public static function warning($message, $context = []) { self::log(self::WARNING, $message, $context); } public static function error($message, $context = []) { self::log(self::ERROR, $message, $context); } public static function critical($message, $context = []) { self::log(self::CRITICAL, $message, $context); } private static function log($level, $message, $context) { // التحقق من أن المستوى مرتفع بما يكفي للتسجيل $levels = [ self::DEBUG => 1, self::INFO => 2, self::WARNING => 3, self::ERROR => 4, self::CRITICAL => 5 ]; if ($levels[$level] < $levels[self::$logLevel]) { return; } $logEntry = [ "timestamp" => date("Y-m-d H:i:s"), "level" => $level, "message" => $message, "context" => $context ]; self::$logs[] = $logEntry; // الكتابة إلى ملف إذا كان ممكناً if (self::$logToFile) { self::writeToFile($logEntry); } } private static function writeToFile($logEntry) { $line = sprintf( "[%s] %s: %s %s\n", $logEntry["timestamp"], $logEntry["level"], $logEntry["message"], !empty($logEntry["context"]) ? json_encode($logEntry["context"]) : "" ); file_put_contents(self::$logFile, $line, FILE_APPEND); } public static function getLogs($level = null) { if ($level === null) { return self::$logs; } return array_filter(self::$logs, function($log) use ($level) { return $log["level"] === $level; }); } public static function clear() { self::$logs = []; } public static function getStats() { $stats = [ self::DEBUG => 0, self::INFO => 0, self::WARNING => 0, self::ERROR => 0, self::CRITICAL => 0 ]; foreach (self::$logs as $log) { $stats[$log["level"]]++; } return $stats; } } // الاستخدام Logger::setLogLevel(Logger::DEBUG); Logger::enableFileLogging("myapp.log"); Logger::debug("بدأ التطبيق"); Logger::info("سجل المستخدم دخوله", ["user_id" => 123]); Logger::warning("استخدام ذاكرة عالي", ["memory" => "85%"]); Logger::error("فشل اتصال قاعدة البيانات", ["host" => "localhost"]); Logger::critical("تعطل النظام!", ["error" => "نفاد الذاكرة"]); // الحصول على جميع السجلات $allLogs = Logger::getLogs(); echo "إجمالي السجلات: " . count($allLogs) . "\n"; // الحصول على الأخطاء فقط $errors = Logger::getLogs(Logger::ERROR); echo "الأخطاء: " . count($errors) . "\n"; // الحصول على الإحصائيات $stats = Logger::getStats(); print_r($stats); ?>
تمرين:

أنشئ صنف Cache مع أعضاء ثابتة:

  • خاصية ثابتة خاصة $cache (مصفوفة لتخزين البيانات المخزنة)
  • دالة ثابتة set($key, $value, $ttl) - تخزين قيمة مع وقت العيش
  • دالة ثابتة get($key) - استرجاع قيمة إذا لم تنته صلاحيتها
  • دالة ثابتة has($key) - التحقق من وجود المفتاح وعدم انتهاء صلاحيته
  • دالة ثابتة delete($key) - إزالة عنصر مخزن
  • دالة ثابتة clear() - مسح جميع التخزين المؤقت
  • دالة ثابتة getStats() - إرجاع النجاحات والإخفاقات وإجمالي العناصر
  • ثوابت لقيم TTL الشائعة (ساعة، يوم، أسبوع)

أفضل الممارسات

إرشادات مهمة:
  • استخدم static باعتدال: الكثير من الأعضاء الثابتة يمكن أن تجعل الاختبار صعباً
  • استخدم للدوال المساعدة: مساعدات الرياضيات، المنسقات، المدققات
  • استخدم للموارد المشتركة: اتصالات قواعد البيانات، التكوين
  • ثوابت للقيم الثابتة: استخدم const للقيم التي لا تتغير أبداً
  • الربط الثابت المتأخر: استخدم static:: عند العمل مع الوراثة
  • تجنب الحالة الثابتة: الخصائص الثابتة تنشئ حالة عامة، استخدمها بحذر

الملخص

في هذا الدرس، تعلمت:

  • الخصائص والدوال الثابتة التي تنتمي إلى الصنف، وليس المثيلات
  • كيفية استخدام عامل :: للوصول الثابت
  • الفرق بين self:: و static::
  • ثوابت الصنف مع الكلمة المفتاحية const
  • تنفيذ نمط الوحدة الواحدة
  • الاستخدامات العملية مثل المصادقة والتسجيل والتكوين
  • أفضل الممارسات للأعضاء الثابتة

بعد ذلك، سنستكشف السمات والدوال السحرية لإعادة استخدام الكود والسلوكيات الخاصة للأصناف!