PHP Fundamentals

Static Members & Constants

13 min Lesson 24 of 45

Understanding Static Members

Static properties and methods belong to the class itself, not to individual objects. They can be accessed without creating an instance of the class.

Key Concepts:
  • Static Property: A variable shared by all instances of a class
  • Static Method: A function that can be called without creating an object
  • Access: Use the :: operator (scope resolution operator)
  • self keyword: Refers to the current class in static context

Static Properties

Static properties are shared across all instances of a class:

<?php class Counter { public static $count = 0; // Static property private $id; public function __construct() { self::$count++; // Increment static property $this->id = self::$count; } public function getId() { return $this->id; } public static function getCount() { return self::$count; } } // Access static property without creating an object echo Counter::$count; // Output: 0 // Create objects $obj1 = new Counter(); $obj2 = new Counter(); $obj3 = new Counter(); // Check static property echo Counter::$count; // Output: 3 echo Counter::getCount(); // Output: 3 echo $obj1->getId(); // Output: 1 echo $obj2->getId(); // Output: 2 echo $obj3->getId(); // Output: 3 ?>
Important: Use self::$property to access static properties inside the class, and ClassName::$property outside the class.

Static Methods

Static methods can be called without creating an instance:

<?php class MathHelper { // Static methods for mathematical operations 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); } } // Call static methods without creating an object echo MathHelper::add(5, 3); // Output: 8 echo MathHelper::multiply(4, 7); // Output: 28 echo MathHelper::power(2, 10); // Output: 1024 echo MathHelper::average(10, 20, 30); // Output: 20 ?>

Practical Example: Database Connection

Static members are commonly used for singleton patterns and shared resources:

<?php class Database { private static $instance = null; private static $connectionCount = 0; private $connection; // Private constructor prevents direct instantiation private function __construct() { self::$connectionCount++; echo "Database connection #{self::$connectionCount} created\n"; // Simulate database connection $this->connection = "MySQL Connection"; } // Get single instance (Singleton pattern) 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 "Executing: {$sql} on {$this->connection}"; } // Prevent cloning private function __clone() {} // Prevent unserialization private function __wakeup() {} } // Get database instance $db1 = Database::getInstance(); // Creates connection #1 $db2 = Database::getInstance(); // Returns same instance $db3 = Database::getInstance(); // Returns same instance echo Database::getConnectionCount(); // Output: 1 (only one connection created) echo $db1->query("SELECT * FROM users"); // All variables point to the same instance var_dump($db1 === $db2); // true var_dump($db2 === $db3); // true ?>
Singleton Pattern: Ensures a class has only one instance and provides a global access point to it. Useful for database connections, configuration managers, and loggers.

Real-World Example: User Authentication

Let's create a comprehensive authentication system using static members:

<?php class Auth { private static $currentUser = null; private static $loginAttempts = []; private static $maxAttempts = 5; private static $lockoutTime = 900; // 15 minutes in seconds // Check if user is logged in public static function isLoggedIn() { return self::$currentUser !== null; } // Get current user public static function getCurrentUser() { return self::$currentUser; } // Login method public static function login($username, $password) { // Check if IP is locked out $ip = self::getClientIP(); if (self::isLockedOut($ip)) { return [ "success" => false, "message" => "Too many failed attempts. Try again later." ]; } // Validate credentials (simplified) if (self::validateCredentials($username, $password)) { self::$currentUser = [ "username" => $username, "login_time" => time(), "ip" => $ip ]; self::clearAttempts($ip); return [ "success" => true, "message" => "Login successful" ]; } // Record failed attempt self::recordAttempt($ip); return [ "success" => false, "message" => "Invalid credentials" ]; } // Logout method public static function logout() { self::$currentUser = null; } // Require authentication public static function requireAuth() { if (!self::isLoggedIn()) { throw new Exception("Authentication required"); } } // Check if user has been logged in for too long public static function isSessionExpired($maxAge = 3600) { if (!self::isLoggedIn()) { return true; } return (time() - self::$currentUser["login_time"]) > $maxAge; } // Private helper methods private static function validateCredentials($username, $password) { // Simplified validation $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; } } // Usage example if (!Auth::isLoggedIn()) { $result = Auth::login("admin", "admin123"); if ($result["success"]) { echo "Welcome, " . Auth::getCurrentUser()["username"]; } else { echo "Login failed: " . $result["message"]; } } // Protect a page try { Auth::requireAuth(); echo "This is a protected page"; } catch (Exception $e) { echo "Access denied: " . $e->getMessage(); } // Check session expiration if (Auth::isSessionExpired(1800)) { // 30 minutes Auth::logout(); echo "Session expired, please login again"; } ?>

Class Constants

Constants are values that cannot be changed once defined:

<?php class Configuration { // Define constants public const APP_NAME = "MyApp"; public const VERSION = "1.0.0"; public const MAX_UPLOAD_SIZE = 5242880; // 5MB in bytes private const API_SECRET = "secret-key-123"; // Can use constants in calculations 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; } // Private constant accessible only inside class public static function getApiUrl() { return "https://api.example.com/" . self::API_SECRET; } } // Access constants echo Configuration::APP_NAME; // Output: MyApp echo Configuration::VERSION; // Output: 1.0.0 echo Configuration::UPLOAD_SIZE_MB; // Output: 5 // Use in methods $fileSize = 6000000; // 6MB if (Configuration::validateUploadSize($fileSize)) { echo "File size OK"; } else { echo "File too large. Max: " . Configuration::UPLOAD_SIZE_MB . "MB"; } ?>
Constants vs Static Properties:
  • Constants: Cannot be changed, use const keyword
  • Static Properties: Can be modified, use static keyword

Enumerations with Constants

Create enumeration-like structures using constants:

<?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 => "Pending", self::PROCESSING => "Processing", self::SHIPPED => "Shipped", self::DELIVERED => "Delivered", self::CANCELLED => "Cancelled" ]; return $labels[$status] ?? "Unknown"; } } 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 ]); } } // Usage $order = new Order(12345); echo $order->getStatusLabel(); // Output: Pending $order->setStatus(OrderStatus::PROCESSING); echo $order->getStatusLabel(); // Output: Processing if ($order->canBeCancelled()) { $order->setStatus(OrderStatus::CANCELLED); echo "Order cancelled"; } // List all statuses foreach (OrderStatus::getAll() as $status) { echo OrderStatus::getLabel($status) . "\n"; } ?>

Late Static Binding

Use static:: instead of self:: for late static binding in inheritance:

<?php class Animal { protected static $species = "Unknown"; public static function getSpecies() { return self::$species; // Early binding - always returns "Unknown" } public static function getSpeciesLate() { return static::$species; // Late binding - returns child class value } public static function create() { return new static(); // Creates instance of called class } } class Dog extends Animal { protected static $species = "Canis familiaris"; } class Cat extends Animal { protected static $species = "Felis catus"; } // Early binding (self::) echo Dog::getSpecies(); // Output: Unknown (uses parent value) echo Cat::getSpecies(); // Output: Unknown (uses parent value) // Late static binding (static::) echo Dog::getSpeciesLate(); // Output: Canis familiaris echo Cat::getSpeciesLate(); // Output: Felis catus // Create instances using late static binding $dog = Dog::create(); // Creates Dog instance $cat = Cat::create(); // Creates Cat instance echo get_class($dog); // Output: Dog echo get_class($cat); // Output: Cat ?>
self vs static:
  • self:: Refers to the class where it's written (early binding)
  • static:: Refers to the class that was called (late binding)
  • Use static:: When you want child classes to use their own values

Practical Example: Logger System

A comprehensive logging system using static members:

<?php class Logger { // Log levels as constants 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) { // Check if level is high enough to log $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; // Write to file if enabled 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; } } // Usage Logger::setLogLevel(Logger::DEBUG); Logger::enableFileLogging("myapp.log"); Logger::debug("Application started"); Logger::info("User logged in", ["user_id" => 123]); Logger::warning("High memory usage", ["memory" => "85%"]); Logger::error("Database connection failed", ["host" => "localhost"]); Logger::critical("System crash!", ["error" => "Out of memory"]); // Get all logs $allLogs = Logger::getLogs(); echo "Total logs: " . count($allLogs) . "\n"; // Get only errors $errors = Logger::getLogs(Logger::ERROR); echo "Errors: " . count($errors) . "\n"; // Get statistics $stats = Logger::getStats(); print_r($stats); ?>
Exercise:

Create a Cache class with static members:

  • Private static property $cache (array to store cached data)
  • Static method set($key, $value, $ttl) - Store value with time-to-live
  • Static method get($key) - Retrieve value if not expired
  • Static method has($key) - Check if key exists and is not expired
  • Static method delete($key) - Remove cached item
  • Static method clear() - Clear all cache
  • Static method getStats() - Return hits, misses, and total items
  • Constants for common TTL values (1 hour, 1 day, 1 week)

Best Practices

Important Guidelines:
  • Use static sparingly: Too many static members can make testing difficult
  • Use for utility functions: Math helpers, formatters, validators
  • Use for shared resources: Database connections, configuration
  • Constants for fixed values: Use const for values that never change
  • Late static binding: Use static:: when working with inheritance
  • Avoid static state: Static properties create global state, use carefully

Summary

In this lesson, you learned:

  • Static properties and methods that belong to the class, not instances
  • How to use the :: operator for static access
  • The difference between self:: and static::
  • Class constants with the const keyword
  • Singleton pattern implementation
  • Practical uses like authentication, logging, and configuration
  • Best practices for static members

Next, we'll explore traits and magic methods for code reuse and special class behaviors!