Skip to main content

Course Progress

Loading...

Static Properties and Methods in PHP

Duration: 45 minutes
Module 2: Object-Oriented PHP

Learning Objectives

  • Master PHP programming concepts
  • Write clean, maintainable code
  • Apply best practices
  • Build dynamic applications

Understanding Class-Level Members: Static Properties and Methods

Welcome to our exploration of static properties and methods in PHP! After learning about the $this keyword and how it relates to object instances, we're now ready to dive into members that belong to the class itself rather than to individual objects.

Static properties and methods play an important role in modern PHP development, especially within the WordPress ecosystem. By the end of this session, you'll understand how they work, when to use them, and how they can make your code more efficient and organized.

What Are Static Members?

Diagram
Class Diagram (Diagram converted to static representation) classDiagram class User { -id -username -email +$t...

Static members are properties and methods that belong to a class as a whole, rather than to a specific object instance. They are declared using the static keyword and are accessed using the scope resolution operator (::) rather than the object operator (->).

The Library Analogy

Think of a class as a library building, with objects as individual books in that library:

  • Regular (instance) properties are like the characteristics of each book — title, author, page count, etc.
  • Regular (instance) methods are like operations you can perform on a specific book — open it, read it, check its condition.
  • Static properties are like characteristics of the library itself — address, total number of books, operation hours.
  • Static methods are like services provided by the library as a whole — search the catalog, get directions to the library, check if it's open.
Class Level (shared across all instances) Static Properties public static $count = 0; Static Methods public static function getCount() Instance Level (unique to each object) Instance Properties public $name, $email, $id; Instance Methods public function getName() Access

The key insight is that static members operate independently of object instances. You don't need to create an object to use them, and they maintain their state across all instances of the class.

Declaring Static Properties and Methods

Static Properties

Static properties are declared using the static keyword:

<?php
class Counter {
    // Static property - shared across all Counter objects
    public static $count = 0;
    
    // Instance property - unique to each Counter object
    public $id;
    
    public function __construct() {
        // Increment the static counter
        self::$count++;
        
        // Assign a unique ID to this instance
        $this->id = self::$count;
    }
}

// Create some Counter objects
$counter1 = new Counter();
$counter2 = new Counter();
$counter3 = new Counter();

// Access the static property directly from the class
echo "Total counters created: " . Counter::$count; // Outputs: 3

// Each instance has its own ID
echo "<br>Counter 1 ID: " . $counter1->id; // Outputs: 1
echo "<br>Counter 2 ID: " . $counter2->id; // Outputs: 2
echo "<br>Counter 3 ID: " . $counter3->id; // Outputs: 3
?>

In this example, $count is a static property that tracks how many Counter objects have been created. It's shared across all instances and can be accessed directly from the class with Counter::$count.

Static Methods

Static methods are declared similarly, using the static keyword:

<?php
class MathHelper {
    // Static method - can be called without creating an object
    public static function square($number) {
        return $number * $number;
    }
    
    public static function cube($number) {
        return $number * $number * $number;
    }
    
    public static function sum(...$numbers) {
        return array_sum($numbers);
    }
}

// Call static methods directly from the class
echo MathHelper::square(4); // Outputs: 16
echo "<br>";
echo MathHelper::cube(3); // Outputs: this27
echo "<br>";
echo MathHelper::sum(1, 2, 3, 4, 5); // Outputs: 15
?>

Static methods are utility functions that don't require an object instance. They're perfect for operations that are related to the class but don't need to access instance properties or methods.

Accessing Static Members

From Outside the Class

When accessing static members from outside the class, you use the class name followed by the scope resolution operator (::):

<?php
class Config {
    // Static properties
    public static $appName = "My WordPress App";
    public static $version = "1.0.0";
    public static $debugMode = false;
    
    // Static method
    public static function getVersion() {
        return self::$version;
    }
    
    // Static method that uses static properties
    public static function getAppInfo() {
        return [
            'name' => self::$appName,
            'version' => self::$version,
            'debug' => self::$debugMode
        ];
    }
    
    // Static method to toggle debug mode
    public static function toggleDebugMode() {
        self::$debugMode = !self::$debugMode;
        return self::$debugMode;
    }
}

// Accessing static properties
echo "App Name: " . Config::$appName; // Outputs: My WordPress App
echo "<br>";
echo "Version: " . Config::$version; // Outputs: 1.0.0
echo "<br>";
echo "Debug Mode: " . (Config::$debugMode ? 'On' : 'Off'); // Outputs: Off

echo "<br><br>";

// Calling static methods
echo "Version via method: " . Config::getVersion(); // Outputs: 1.0.0
echo "<br>";

// Get app info as an array
$appInfo = Config::getAppInfo();
echo "App Info: " . print_r($appInfo, true);

echo "<br><br>";

// Toggle debug mode
echo "Debug Mode toggled: " . (Config::toggleDebugMode() ? 'On' : 'Off'); // Outputs: On
echo "<br>";
echo "Debug Mode toggled again: " . (Config::toggleDebugMode() ? 'On' : 'Off'); // Outputs: Off
?>

From Inside the Class

When accessing static members from within the class, you use the self keyword followed by the scope resolution operator:

<?php
class User {
    // Static property to track user count
    private static $count = 0;
    
    // Static property to store admin user IDs
    private static $adminIds = [1, 2, 3];
    
    // Instance properties
    private $id;
    private $username;
    private $isAdmin;
    
    public function __construct($username) {
        // Increment the user count
        self::$count++;
        
        // Assign properties
        $this->id = self::$count;
        $this->username = $username;
        
        // Check if this user is an admin
        $this->isAdmin = in_array($this->id, self::$adminIds);
    }
    
    // Static method to get user count
    public static function getCount() {
        return self::$count;
    }
    
    // Static method to add an admin ID
    public static function addAdminId($id) {
        if (!in_array($id, self::$adminIds)) {
            self::$adminIds[] = $id;
            return true;
        }
        return false;
    }
    
    // Instance method that checks admin status
    public function isAdmin() {
        // Re-check admin status (in case adminIds was updated)
        $this->isAdmin = in_array($this->id, self::$adminIds);
        return $this->isAdmin;
    }
    
    // Instance method that calls a static method
    public function displayInfo() {
        return "User {$this->username} (ID: {$this->id}) is " . 
               ($this->isAdmin() ? "an admin" : "not an admin") . 
               ". Total users: " . self::getCount();
    }
}

// Create some users
$user1 = new User("admin");
$user2 = new User("jane");
$user3 = new User("john");

// Display user info
echo $user1->displayInfo(); // admin is an admin
echo "<br>";
echo $user2->displayInfo(); // jane is not an admin
echo "<br>";
echo $user3->displayInfo(); // john is not an admin

echo "<br><br>";

// Add user3 as an admin
User::addAdminId(3);
echo "After adding user 3 as admin:<br>";
echo $user3->displayInfo(); // Now john is an admin
?>

In this example, we use self:: to access static properties and methods from within the class, both in static methods and in instance methods.

Through an Object Instance (Not Recommended)

While not recommended, you can technically access static members through an object instance:

<?php
class Logger {
    public static $logCount = 0;
    
    public static function log($message) {
        self::$logCount++;
        echo "[LOG #{" . self::$logCount . "}] $message<br>";
    }
}

// Create an instance
$logger = new Logger();

// These do the same thing
Logger::log("Accessing static method through the class (recommended)");
$logger->log("Accessing static method through an object (not recommended)");

// These also do the same thing
echo "Log count via class: " . Logger::$logCount . "<br>";
echo "Log count via object: " . $logger::$logCount . "<br>";
?>

While PHP allows accessing static members through an object, it's confusing and can lead to errors. Always use the class name to access static members for clarity.

Common Use Cases for Static Members

1. Counters and Tracking

Static properties are excellent for keeping track of class-wide statistics:

<?php
class PageView {
    private static $totalViews = 0;
    private static $viewsByPage = [];
    
    private $page;
    private $timestamp;
    private $userIp;
    
    public function __construct($page) {
        $this->page = $page;
        $this->timestamp = time();
        $this->userIp = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
        
        // Increment total views
        self::$totalViews++;
        
        // Increment views for this page
        if (!isset(self::$viewsByPage[$page])) {
            self::$viewsByPage[$page] = 0;
        }
        self::$viewsByPage[$page]++;
    }
    
    public static function getTotalViews() {
        return self::$totalViews;
    }
    
    public static function getViewsForPage($page) {
        return self::$viewsByPage[$page] ?? 0;
    }
    
    public static function getMostViewedPage() {
        if (empty(self::$viewsByPage)) {
            return null;
        }
        
        $maxViews = max(self::$viewsByPage);
        return array_search($maxViews, self::$viewsByPage);
    }
}

// Record some page views
new PageView('home');
new PageView('about');
new PageView('home');
new PageView('contact');
new PageView('home');

// Get statistics
echo "Total page views: " . PageView::getTotalViews(); // 5
echo "<br>";
echo "Views for home page: " . PageView::getViewsForPage('home'); // 3
echo "<br>";
echo "Most viewed page: " . PageView::getMostViewedPage(); // home
?>

2. Configuration Settings

Static properties and methods are perfect for storing and retrieving configuration settings:

<?php
class AppSettings {
    private static $settings = [
        'site_name' => 'My WordPress Site',
        'theme' => 'twentytwentyfive',
        'debug' => false,
        'posts_per_page' => 10,
        'api_key' => null
    ];
    
    public static function get($key, $default = null) {
        return self::$settings[$key] ?? $default;
    }
    
    public static function set($key, $value) {
        self::$settings[$key] = $value;
    }
    
    public static function load($file) {
        if (file_exists($file)) {
            $newSettings = include $file;
            if (is_array($newSettings)) {
                self::$settings = array_merge(self::$settings, $newSettings);
                return true;
            }
        }
        return false;
    }
    
    public static function getAll() {
        return self::$settings;
    }
}

// Get settings
echo "Site name: " . AppSettings::get('site_name'); // My WordPress Site
echo "<br>";
echo "Posts per page: " . AppSettings::get('posts_per_page'); // 10

// Change a setting
AppSettings::set('debug', true);
AppSettings::set('api_key', 'abc123xyz');

// Check updated settings
echo "<br><br>";
echo "Debug mode: " . (AppSettings::get('debug') ? 'On' : 'Off'); // On
echo "<br>";
echo "API Key: " . AppSettings::get('api_key'); // abc123xyz
?>

3. Factory Methods

Static methods are great for creating and returning instances of a class (factory pattern):

<?php
class Post {
    private $id;
    private $title;
    private $content;
    private $author;
    private $date;
    
    // Private constructor - can't create directly
    private function __construct($id, $title, $content, $author, $date) {
        $this->id = $id;
        $this->title = $title;
        $this->content = $content;
        $this->author = $author;
        $this->date = $date;
    }
    
    // Factory method to create a post from an array
    public static function fromArray($data) {
        return new self(
            $data['id'] ?? 0,
            $data['title'] ?? '',
            $data['content'] ?? '',
            $data['author'] ?? 'Unknown',
            $data['date'] ?? date('Y-m-d H:i:s')
        );
    }
    
    // Factory method to create a post from a database row
    public static function fromDatabase($id) {
        // In a real app, this would query the database
        // Simulating a database fetch
        $data = [
            'id' => $id,
            'title' => "Post #$id",
            'content' => "This is the content of post #$id",
            'author' => "Author of post #$id",
            'date' => date('Y-m-d H:i:s')
        ];
        
        return self::fromArray($data);
    }
    
    // Factory method to create a new draft
    public static function createDraft($title, $author) {
        return new self(
            0, // No ID yet
            $title,
            '', // Empty content
            $author,
            date('Y-m-d H:i:s')
        );
    }
    
    // Instance method to get post info
    public function getInfo() {
        return [
            'id' => $this->id,
            'title' => $this->title,
            'author' => $this->author,
            'date' => $this->date,
            'excerpt' => substr($this->content, 0, 50) . '...'
        ];
    }
}

// Using factory methods
$post1 = Post::fromDatabase(123);
$post2 = Post::createDraft("New Post Draft", "Jane Author");

$postData = [
    'id' => 456,
    'title' => 'Custom Post',
    'content' => 'This is a custom post created from an array.',
    'author' => 'John Writer'
];
$post3 = Post::fromArray($postData);

// Display post info
echo "Post 1 info:<br>";
print_r($post1->getInfo());

echo "<br><br>Post 2 info:<br>";
print_r($post2->getInfo());

echo "<br><br>Post 3 info:<br>";
print_r($post3->getInfo());
?>

4. Utility Classes

Static methods are ideal for utility functions that don't require object state:

<?php
class StringHelper {
    public static function slug($string) {
        $string = strtolower($string);
        $string = preg_replace('/[^a-z0-9\-]/', '-', $string);
        $string = preg_replace('/-+/', '-', $string);
        return trim($string, '-');
    }
    
    public static function truncate($string, $length = 100, $append = '...') {
        if (strlen($string) <= $length) {
            return $string;
        }
        
        $string = substr($string, 0, $length);
        $pos = strrpos($string, ' ');
        
        if ($pos !== false) {
            $string = substr($string, 0, $pos);
        }
        
        return $string . $append;
    }
    
    public static function randomString($length = 10) {
        $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $result = '';
        
        for ($i = 0; $i < $length; $i++) {
            $result .= $characters[rand(0, strlen($characters) - 1)];
        }
        
        return $result;
    }
}

// Using the utility methods
$title = "How to Use Static Methods in PHP and WordPress";
echo "Original: $title<br>";
echo "Slug: " . StringHelper::slug($title) . "<br>";
echo "Truncated: " . StringHelper::truncate($title, 20) . "<br>";
echo "Random string: " . StringHelper::randomString(8) . "<br>";
?>

Static vs. Instance Members: When to Use Each

Diagram
Yes No Yes No Yes No Need to decide: Static or Instance? Use Instance Method/Property Use Static Method Use Static Property Use Instance Property Does it need object state? Is it utility functionality? Is it shared across instances?

Use Static Members When:

  • The functionality doesn't require an object instance
  • You need to share data across all instances of a class
  • You're implementing utility functions related to a class
  • You need to track class-wide statistics or counts
  • You're implementing the Singleton pattern or factory methods

Use Instance Members When:

  • The functionality is specific to an object instance
  • You need to maintain state for each object
  • You're implementing behavior that varies by instance
  • You need to use the $this keyword
<?php
class BankAccount {
    // Static property - tracks all accounts
    private static $totalAccounts = 0;
    private static $bank = "Example Bank";
    
    // Instance properties - specific to each account
    private $accountNumber;
    private $balance;
    private $owner;
    
    // Constructor uses both static and instance
    public function __construct($owner, $initialDeposit = 0) {
        // Increment static counter
        self::$totalAccounts++;
        
        // Set instance properties
        $this->accountNumber = self::generateAccountNumber();
        $this->owner = $owner;
        $this->balance = $initialDeposit;
    }
    
    // Static method - not tied to a specific account
    public static function getBankName() {
        return self::$bank;
    }
    
    // Static method - not tied to a specific account
    public static function getTotalAccounts() {
        return self::$totalAccounts;
    }
    
    // Static utility method
    private static function generateAccountNumber() {
        return 'ACCT-' . str_pad(self::$totalAccounts, 6, '0', STR_PAD_LEFT);
    }
    
    // Instance method - specific to an account
    public function deposit($amount) {
        if ($amount <= 0) {
            return false;
        }
        
        $this->balance += $amount;
        return true;
    }
    
    // Instance method - specific to an account
    public function withdraw($amount) {
        if ($amount <= 0 || $amount > $this->balance) {
            return false;
        }
        
        $this->balance -= $amount;
        return true;
    }
    
    // Instance method - specific to an account
    public function getBalance() {
        return $this->balance;
    }
    
    // Instance method - specific to an account
    public function getAccountDetails() {
        return [
            'bank' => self::$bank, // Static property
            'account_number' => $this->accountNumber,
            'owner' => $this->owner,
            'balance' => $this->balance
        ];
    }
}

// Using the class
echo "Bank name: " . BankAccount::getBankName() . "<br>";
echo "Total accounts before: " . BankAccount::getTotalAccounts() . "<br>";

// Create some accounts
$account1 = new BankAccount("John Doe", 1000);
$account2 = new BankAccount("Jane Smith", 500);

echo "Total accounts after: " . BankAccount::getTotalAccounts() . "<br><br>";

// Use instance methods
$account1->deposit(250);
$account2->withdraw(100);

echo "Account 1 balance: $" . $account1->getBalance() . "<br>";
echo "Account 2 balance: $" . $account2->getBalance() . "<br><br>";

// Get account details
$details1 = $account1->getAccountDetails();
echo "Account 1 details: " . print_r($details1, true) . "<br>";
?>

This example demonstrates when to use static vs. instance members in a real-world scenario. The bank name and total accounts are static (shared across all accounts), while account balances and transactions are instance-specific.

Static Members in WordPress Development

WordPress makes extensive use of static members in its core code and plugins. Let's look at some common patterns:

1. Helper Functions and Utilities

<?php
/**
 * Static utility class for WordPress post operations
 */
class Post_Helper {
    /**
     * Get posts by category slug
     * 
     * @param string $category_slug The category slug
     * @param int $limit How many posts to retrieve
     * @return array Array of post objects
     */
    public static function get_posts_by_category($category_slug, $limit = 5) {
        $args = [
            'category_name' => $category_slug,
            'posts_per_page' => $limit,
            'post_status' => 'publish'
        ];
        
        $query = new WP_Query($args);
        return $query->posts;
    }
    
    /**
     * Get related posts based on tags
     * 
     * @param int $post_id The post ID to find related posts for
     * @param int $limit How many related posts to find
     * @return array Array of related post objects
     */
    public static function get_related_posts($post_id, $limit = 3) {
        $tags = wp_get_post_tags($post_id);
        
        if (empty($tags)) {
            return [];
        }
        
        $tag_ids = [];
        foreach ($tags as $tag) {
            $tag_ids[] = $tag->term_id;
        }
        
        $args = [
            'tag__in' => $tag_ids,
            'post__not_in' => [$post_id],
            'posts_per_page' => $limit,
            'post_status' => 'publish'
        ];
        
        $query = new WP_Query($args);
        return $query->posts;
    }
    
    /**
     * Format a post date
     * 
     * @param string $post_date The post date
     * @param string $format Date format (default WordPress date format)
     * @return string Formatted date
     */
    public static function format_post_date($post_date, $format = '') {
        if (empty($format)) {
            $format = get_option('date_format');
        }
        
        return date_i18n($format, strtotime($post_date));
    }
    
    /**
     * Generate an excerpt from content
     * 
     * @param string $content The content to create excerpt from
     * @param int $length Maximum excerpt length
     * @return string Formatted excerpt
     */
    public static function generate_excerpt($content, $length = 55) {
        $content = strip_shortcodes($content);
        $content = strip_tags($content);
        $words = explode(' ', $content, $length + 1);
        
        if (count($words) > $length) {
            array_pop($words);
            $content = implode(' ', $words) . '...';
        }
        
        return $content;
    }
}

// Using the helper class
$category_posts = Post_Helper::get_posts_by_category('news', 3);
$related_posts = Post_Helper::get_related_posts(get_the_ID(), 4);
$formatted_date = Post_Helper::format_post_date(get_the_date('Y-m-d'), 'F j, Y');
$excerpt = Post_Helper::generate_excerpt(get_the_content(), 30);
?>

2. Singleton Pattern for Plugin Main Classes

<?php
/**
 * Main plugin class using the Singleton pattern with static methods
 */
class My_Plugin {
    // Static property to hold the single instance
    private static $instance = null;
    
    // Private properties
    private $plugin_path;
    private $plugin_url;
    private $version = '1.0.0';
    
    /**
     * Private constructor - can't create directly
     */
    private function __construct() {
        $this->plugin_path = plugin_dir_path(__FILE__);
        $this->plugin_url = plugin_dir_url(__FILE__);
        
        // Hook into WordPress
        add_action('init', [$this, 'init']);
        add_action('admin_menu', [$this, 'admin_menu']);
        add_action('wp_enqueue_scripts', [$this, 'enqueue_scripts']);
    }
    
    /**
     * Get the singleton instance
     * 
     * @return My_Plugin The single plugin instance
     */
    public static function get_instance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        
        return self::$instance;
    }
    
    /**
     * Static method to activate the plugin
     */
    public static function activate() {
        // Activation code
        flush_rewrite_rules();
    }
    
    /**
     * Static method to deactivate the plugin
     */
    public static function deactivate() {
        // Deactivation code
        flush_rewrite_rules();
    }
    
    /**
     * Initialize the plugin
     */
    public function init() {
        // Register post types, taxonomies, etc.
    }
    
    /**
     * Set up admin menu
     */
    public function admin_menu() {
        add_options_page(
            'My Plugin Settings',
            'My Plugin',
            'manage_options',
            'my-plugin',
            [$this, 'render_settings_page']
        );
    }
    
    /**
     * Enqueue frontend scripts and styles
     */
    public function enqueue_scripts() {
        wp_enqueue_style(
            'my-plugin-style',
            $this->plugin_url . 'assets/css/style.css',
            [],
            $this->version
        );
    }
    
    /**
     * Render the settings page
     */
    public function render_settings_page() {
        // Output settings page HTML
    }
    
    /**
     * Get the plugin path
     * 
     * @return string The plugin path
     */
    public function get_plugin_path() {
        return $this->plugin_path;
    }
    
    /**
     * Get the plugin URL
     * 
     * @return string The plugin URL
     */
    public function get_plugin_url() {
        return $this->plugin_url;
    }
}

// Plugin setup
register_activation_hook(__FILE__, ['My_Plugin', 'activate']);
register_deactivation_hook(__FILE__, ['My_Plugin', 'deactivate']);

// Initialize the plugin using the static method
function my_plugin() {
    return My_Plugin::get_instance();
}

// Start the plugin
my_plugin();
?>

3. Template Tags and View Helpers

<?php
/**
 * Static class for template helpers (similar to WordPress template tags)
 */
class Theme_Helper {
    /**
     * Display a post thumbnail with fallback
     * 
     * @param int $post_id The post ID
     * @param string $size Thumbnail size
     * @param string $fallback_img Fallback image URL
     */
    public static function the_thumbnail($post_id = null, $size = 'thumbnail', $fallback_img = '') {
        if (null === $post_id) {
            $post_id = get_the_ID();
        }
        
        if (has_post_thumbnail($post_id)) {
            echo get_the_post_thumbnail($post_id, $size);
        } elseif (!empty($fallback_img)) {
            echo '<img src="' . esc_url($fallback_img) . '" alt="' . esc_attr(get_the_title($post_id)) . '" class="fallback-thumbnail" />';
        }
    }
    
    /**
     * Display social sharing links
     * 
     * @param int $post_id The post ID
     * @param array $networks Which networks to include
     */
    public static function the_social_links($post_id = null, $networks = ['facebook', 'twitter', 'linkedin']) {
        if (null === $post_id) {
            $post_id = get_the_ID();
        }
        
        $permalink = get_permalink($post_id);
        $title = get_the_title($post_id);
        
        echo '<div class="social-share-buttons">';
        
        if (in_array('facebook', $networks)) {
            $facebook_url = 'https://www.facebook.com/sharer/sharer.php?u=' . urlencode($permalink);
            echo '<a href="' . esc_url($facebook_url) . '" target="_blank" class="share-facebook">Facebook</a>';
        }
        
        if (in_array('twitter', $networks)) {
            $twitter_url = 'https://twitter.com/intent/tweet?url=' . urlencode($permalink) . '&text=' . urlencode($title);
            echo '<a href="' . esc_url($twitter_url) . '" target="_blank" class="share-twitter">Twitter</a>';
        }
        
        if (in_array('linkedin', $networks)) {
            $linkedin_url = 'https://www.linkedin.com/shareArticle?mini=true&url=' . urlencode($permalink) . '&title=' . urlencode($title);
            echo '<a href="' . esc_url($linkedin_url) . '" target="_blank" class="share-linkedin">LinkedIn</a>';
        }
        
        echo '</div>';
    }
    
    /**
     * Display a formatted entry date
     * 
     * @param int $post_id The post ID
     * @param bool $modified Whether to show modified date
     */
    public static function the_entry_date($post_id = null, $modified = false) {
        if (null === $post_id) {
            $post_id = get_the_ID();
        }
        
        $time_string = '<time class="entry-date published" datetime="%1$s">%2$s</time>';
        
        if ($modified) {
            $time_string .= '<time class="updated" datetime="%3$s">%4$s</time>';
        }
        
        $time_string = sprintf(
            $time_string,
            esc_attr(get_the_date('c', $post_id)),
            esc_html(get_the_date('', $post_id)),
            esc_attr(get_the_modified_date('c', $post_id)),
            esc_html(get_the_modified_date('', $post_id))
        );
        
        echo $time_string;
    }
    
    /**
     * Get author bio with fallback
     * 
     * @param int $author_id The author ID
     * @param string $fallback Fallback text
     * @return string The author bio
     */
    public static function get_author_bio($author_id = null, $fallback = '') {
        if (null === $author_id) {
            $author_id = get_the_author_meta('ID');
        }
        
        $bio = get_the_author_meta('description', $author_id);
        
        if (empty($bio) && !empty($fallback)) {
            return $fallback;
        }
        
        return $bio;
    }
}

// Using the template helpers in a theme
// In single.php:
Theme_Helper::the_thumbnail(null, 'large', get_template_directory_uri() . '/images/default.jpg');
Theme_Helper::the_entry_date();
Theme_Helper::the_social_links();
$author_bio = Theme_Helper::get_author_bio();
?>

Common Pitfalls and Gotchas

1. Using $this in Static Methods

<?php
class StaticExample {
    public $instanceProperty = "Instance Property";
    public static $staticProperty = "Static Property";
    
    // This will cause a fatal error
    public static function wrongMethod() {
        // Error: Using $this when not in object context
        return $this->instanceProperty;
    }
    
    // This is correct
    public static function rightMethod() {
        // Accessing a static property in a static method
        return self::$staticProperty;
    }
}

// This would cause an error if called
// StaticExample::wrongMethod();

// This works fine
echo StaticExample::rightMethod();
?>

The $this keyword refers to the current object instance, but static methods aren't tied to an instance. They belong to the class itself, so $this doesn't exist in a static context.

2. Modifying Static Properties

<?php
class StaticModification {
    // Array property
    public static $items = ['apple', 'banana', 'cherry'];
    
    // This works as expected
    public static function addItem($item) {
        self::$items[] = $item;
    }
    
    // This doesn't work as expected
    public function addItemIncorrectly($item) {
        // This creates a COPY of the static property as an instance property
        $this->items[] = $item;
        
        // self::$items is NOT modified
    }
}

// Initial state
echo "Initial items: " . implode(', ', StaticModification::$items) . "<br>";

// Add an item using the static method
StaticModification::addItem('orange');
echo "After static method: " . implode(', ', StaticModification::$items) . "<br>";

// Create an instance and try to add an item incorrectly
$instance = new StaticModification();
$instance->addItemIncorrectly('grape');

// Check the static property - grape is not added!
echo "After instance method: " . implode(', ', StaticModification::$items) . "<br>";
?>

When modifying static properties, always use the self:: syntax, even in instance methods. Using $this->property on a static property creates a separate instance property that has no relation to the static one.

3. Inheritance and Static Methods

<?php
class BaseClass {
    public static function whoAmI() {
        return "I am " . get_called_class();
    }
    
    public static function getInfo() {
        // self:: refers to the class where the method is defined
        return "Info from " . self::whoAmI();
    }
    
    public static function getInfoStatic() {
        // static:: refers to the class that was called
        return "Info from " . static::whoAmI();
    }
}

class ChildClass extends BaseClass {
    public static function whoAmI() {
        return "ChildClass";
    }
}

// Using late static binding
echo "BaseClass::getInfo(): " . BaseClass::getInfo() . "<br>";
echo "ChildClass::getInfo(): " . ChildClass::getInfo() . "<br>";
echo "BaseClass::getInfoStatic(): " . BaseClass::getInfoStatic() . "<br>";
echo "ChildClass::getInfoStatic(): " . ChildClass::getInfoStatic() . "<br>";
?>

Output:

BaseClass::getInfo(): Info from I am BaseClass
ChildClass::getInfo(): Info from I am BaseClass
BaseClass::getInfoStatic(): Info from I am BaseClass
ChildClass::getInfoStatic(): Info from ChildClass

This demonstrates late static binding with the static:: keyword. Use self:: to reference the current class and static:: to reference the called class (which might be a child class).

4. Static Properties Are Shared Across All Instances

<?php
class SharedProperty {
    public static $counter = 0;
    public $instanceCounter = 0;
    
    public function increment() {
        self::$counter++;
        $this->instanceCounter++;
    }
}

// Create two instances
$obj1 = new SharedProperty();
$obj2 = new SharedProperty();

// Increment counters
$obj1->increment();
$obj1->increment();
$obj2->increment();

// Check the values
echo "Static counter: " . SharedProperty::$counter . "<br>"; // 3
echo "Object 1 instance counter: " . $obj1->instanceCounter . "<br>"; // 2
echo "Object 2 instance counter: " . $obj2->instanceCounter . "<br>"; // 1
?>

Remember that static properties are shared across all instances of a class. Modifying a static property in one instance affects it everywhere.

Best Practices for Using Static Members

1. Use Static Members for Utility Functions

Static methods are perfect for utility functions that don't need instance state:

<?php
class TextUtility {
    public static function slugify($text) {
        $text = strtolower($text);
        $text = preg_replace('/[^a-z0-9\-]/', '-', $text);
        $text = preg_replace('/-+/', '-', $text);
        return trim($text, '-');
    }
    
    public static function excerpt($text, $length = 55) {
        return self::truncateWords($text, $length);
    }
    
    private static function truncateWords($text, $length) {
        $text = strip_tags($text);
        $words = explode(' ', $text, $length + 1);
        
        if (count($words) > $length) {
            array_pop($words);
            $text = implode(' ', $words) . '...';
        }
        
        return $text;
    }
}

// Using the utility class
$title = "How to Use Static Methods in WordPress Development";
$slug = TextUtility::slugify($title);
$excerpt = TextUtility::excerpt($title, 5);

echo "Title: $title<br>";
echo "Slug: $slug<br>";
echo "Excerpt: $excerpt<br>";
?>

2. Use the Singleton Pattern When Appropriate

The Singleton pattern ensures a class has only one instance and provides a global point of access to it:

<?php
class Database {
    // Store the singleton instance
    private static $instance = null;
    
    // Private connection object
    private $connection;
    
    // Private constructor
    private function __construct() {
        // Connect to the database
        $this->connection = new PDO('mysql:host=localhost;dbname=wordpress', 'username', 'password');
        $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->connection->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    }
    
    // Get the singleton instance
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        
        return self::$instance;
    }
    
    // Query the database
    public function query($sql, $params = []) {
        $stmt = $this->connection->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }
    
    // Get a single row
    public function getRow($sql, $params = []) {
        $stmt = $this->query($sql, $params);
        return $stmt->fetch();
    }
    
    // Get multiple rows
    public function getRows($sql, $params = []) {
        $stmt = $this->query($sql, $params);
        return $stmt->fetchAll();
    }
    
    // Prevent cloning of the instance
    private function __clone() {}
    
    // Prevent unserializing of the instance
    private function __wakeup() {}
}

// Using the singleton
$db = Database::getInstance();
$users = $db->getRows("SELECT * FROM wp_users LIMIT 5");

// Getting the same instance
$sameDb = Database::getInstance();
$options = $sameDb->getRows("SELECT * FROM wp_options LIMIT 5");
?>

3. Document Static Members Clearly

Use PHPDoc comments to document your static properties and methods:

<?php
/**
 * Helper class for working with dates in WordPress
 */
class DateHelper {
    /**
     * Format for MySQL date (YYYY-MM-DD)
     * 
     * @var string
     */
    public static $MYSQL_DATE_FORMAT = 'Y-m-d';
    
    /**
     * Format for MySQL datetime (YYYY-MM-DD HH:MM:SS)
     * 
     * @var string
     */
    public static $MYSQL_DATETIME_FORMAT = 'Y-m-d H:i:s';
    
    /**
     * Convert a date string to MySQL format
     * 
     * @param string $date The date string to convert
     * @param bool $include_time Whether to include time
     * @return string MySQL formatted date
     */
    public static function toMysqlFormat($date, $include_time = false) {
        $timestamp = strtotime($date);
        $format = $include_time ? self::$MYSQL_DATETIME_FORMAT : self::$MYSQL_DATE_FORMAT;
        return date($format, $timestamp);
    }
    
    /**
     * Format a date using WordPress settings
     * 
     * @param string $date The date to format
     * @param string $format Optional format override
     * @return string Formatted date
     */
    public static function formatDate($date, $format = '') {
        if (empty($format)) {
            $format = get_option('date_format');
        }
        
        return date_i18n($format, strtotime($date));
    }
    
    /**
     * Get time ago in human-readable format
     * 
     * @param string $date_string The date
     * @return string Human-readable time difference
     */
    public static function timeAgo($date_string) {
        $timestamp = strtotime($date_string);
        $current_time = current_time('timestamp');
        $diff = $current_time - $timestamp;
        
        if ($diff < 60) {
            return 'just now';
        } elseif ($diff < 3600) {
            $minutes = round($diff / 60);
            return $minutes . ' minute' . ($minutes > 1 ? 's' : '') . ' ago';
        } elseif ($diff < 86400) {
            $hours = round($diff / 3600);
            return $hours . ' hour' . ($hours > 1 ? 's' : '') . ' ago';
        } elseif ($diff < 604800) {
            $days = round($diff / 86400);
            return $days . ' day' . ($days > 1 ? 's' : '') . ' ago';
        } else {
            return self::formatDate($date_string);
        }
    }
}

// Using the helper class
$now = current_time('mysql');
$post_date = '2025-03-15 10:30:45';

echo "Current time: " . DateHelper::formatDate($now) . "<br>";
echo "Post date: " . DateHelper::formatDate($post_date) . "<br>";
echo "Post time ago: " . DateHelper::timeAgo($post_date) . "<br>";
echo "MySQL format: " . DateHelper::toMysqlFormat('April 15, 2025', true) . "<br>";
?>

4. Avoid Overusing Statics

While static members are useful, they can make testing more difficult and can lead to tightly coupled code:

<?php
// AVOID: Too many static methods that should be instance methods
class BadPostManager {
    public static function getPost($id) {
        return get_post($id);
    }
    
    public static function updatePost($id, $data) {
        return wp_update_post(['ID' => $id] + $data);
    }
    
    public static function deletePost($id) {
        return wp_delete_post($id);
    }
}

// BETTER: Use instance methods for operations on specific posts
class BetterPostManager {
    private $post_id;
    private $post;
    
    public function __construct($post_id) {
        $this->post_id = $post_id;
        $this->post = get_post($post_id);
    }
    
    public function update($data) {
        $result = wp_update_post(['ID' => $this->post_id] + $data);
        $this->post = get_post($this->post_id);
        return $result;
    }
    
    public function delete() {
        return wp_delete_post($this->post_id);
    }
    
    public function getData() {
        return $this->post;
    }
    
    // Static factory method
    public static function load($post_id) {
        return new self($post_id);
    }
}

// Using the better approach
$post = BetterPostManager::load(123);
$post->update([
    'post_title' => 'Updated Title',
    'post_content' => 'Updated content...'
]);
?>

Homework: Create a Utility Class with Static Methods

Now it's your turn to practice using static properties and methods:

Assignment

Create a WP_Image_Helper utility class with static methods that make it easier to work with images in WordPress. Your class should:

  1. Include static properties for default image sizes and formats
  2. Include static methods for common image operations
  3. Use proper documentation with PHPDoc comments
  4. Follow WordPress coding standards

Here's a starter template to help you:

<?php
/**
 * WordPress Image Helper Utility Class
 * 
 * A collection of static methods for working with images in WordPress.
 */
class WP_Image_Helper {
    /**
     * Default image size names
     * 
     * @var array
     */
    public static $image_sizes = [
        'thumbnail',
        'medium',
        'large',
        'full'
    ];
    
    /**
     * Default image placeholder
     * 
     * @var string
     */
    public static $placeholder_image = '/assets/images/placeholder.jpg';
    
    /**
     * Get image URL by ID
     * 
     * @param int $attachment_id Image attachment ID
     * @param string $size Image size
     * @return string|false Image URL or false if not found
     */
    public static function get_image_url($attachment_id, $size = 'full') {
        // TODO: Implement this method
    }
    
    /**
     * Get image HTML with fallback
     * 
     * @param int $attachment_id Image attachment ID
     * @param string $size Image size
     * @param string $class CSS class
     * @param string $alt Alt text
     * @return string Image HTML
     */
    public static function get_image_html($attachment_id, $size = 'medium', $class = '', $alt = '') {
        // TODO: Implement this method
    }
    
    /**
     * Get post featured image URL with fallback
     * 
     * @param int $post_id Post ID
     * @param string $size Image size
     * @param string $default_image Default image if no featured image
     * @return string Image URL
     */
    public static function get_featured_image_url($post_id = null, $size = 'large', $default_image = '') {
        // TODO: Implement this method
    }
    
    /**
     * Get all image sizes available in WordPress
     * 
     * @param bool $include_dimensions Whether to include width and height
     * @return array Available image sizes
     */
    public static function get_available_image_sizes($include_dimensions = false) {
        // TODO: Implement this method
    }
    
    /**
     * Check if an image exists in the media library by URL
     * 
     * @param string $url Image URL to check
     * @return int|false Attachment ID if found, false otherwise
     */
    public static function get_attachment_id_from_url($url) {
        // TODO: Implement this method
    }
    
    /**
     * Get image dimensions
     * 
     * @param int $attachment_id Image attachment ID
     * @param string $size Image size
     * @return array|false Array with width and height, or false if not found
     */
    public static function get_image_dimensions($attachment_id, $size = 'full') {
        // TODO: Implement this method
    }
}

// Example usage
$image_url = WP_Image_Helper::get_featured_image_url(get_the_ID(), 'large');
$image_html = WP_Image_Helper::get_image_html(123, 'medium', 'aligncenter', 'My Image');
$image_sizes = WP_Image_Helper::get_available_image_sizes(true);
?>

Complete the implementation of this class, providing the functionality for each method. Consider how static properties and methods can make working with WordPress images easier.

Additional Resources

Further Reading

Next Class Preview

In our next session, we'll explore inheritance in PHP, which allows classes to inherit properties and methods from parent classes. This is a powerful feature that enables code reuse and hierarchical relationships between classes.