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!