Constructor and Destructor Methods in PHP
Learning Objectives
- Master PHP programming concepts
- Write clean, maintainable code
- Apply best practices
- Build dynamic applications
The Birth and Death of Objects: Constructors and Destructors
Welcome to our deep dive into constructors and destructors! These special methods are crucial for properly initializing and cleaning up objects in PHP. Whether you're building WordPress plugins, themes, or custom applications, mastering these concepts will help you write more efficient and reliable code.
Think of constructors and destructors as the bookends of an object's lifecycle. The constructor is like a birth certificate—officiating an object's arrival and setting up its initial conditions. The destructor is like a will—handling final tasks before the object fades away.
Constructors: Setting the Stage
What Are Constructors?
A constructor is a special method that is automatically called when an object is created using the new keyword. In PHP, constructors are defined using the __construct() method name.
The Anatomy of a Constructor
<?php
class BlogPost {
// Properties
public $title;
public $content;
public $author;
public $publishDate;
// Constructor
public function __construct($title, $content, $author = 'Anonymous', $publishDate = null) {
// Initialize properties
$this->title = $title;
$this->content = $content;
$this->author = $author;
// Set default value if none provided
$this->publishDate = $publishDate ?: date('Y-m-d H:i:s');
// Additional initialization logic
$this->sanitizeContent();
echo "A new BlogPost object has been created!
";
}
private function sanitizeContent() {
// Sanitize the content
$this->content = strip_tags($this->content, '
Key Constructor Features
- Automatic invocation: Called implicitly when creating a new object
- Property initialization: Sets initial state of the object
- Parameter handling: Can accept arguments to customize initialization
- Default values: Can provide fallbacks for optional parameters
- Complex initialization: Can call other methods or perform complex logic
The Evolution of PHP Constructors
If you work with legacy code or older WordPress plugins, you might encounter an older constructor style:
<?php
class OldStyleClass {
// Old-style constructor (PHP 4 style)
public function OldStyleClass() {
echo "This is an old-style constructor";
}
}
class ModernClass {
// Modern constructor (PHP 5+ style)
public function __construct() {
echo "This is a modern constructor";
}
}
// Both will work in PHP 5.x, but the old style is deprecated in PHP 7+
$old = new OldStyleClass(); // Works in older versions, deprecated in PHP 7+
$new = new ModernClass(); // The recommended approach
?>
Best Practice
Always use __construct() for constructors in your PHP code. The old style constructors were deprecated in PHP 7.0 and were removed in PHP 8.0. WordPress now requires PHP 7.0+ for newer versions, so it's important to follow modern practices.
Constructors in WordPress Development
Constructors play a vital role in WordPress plugin and theme development. Let's explore some common patterns:
Plugin Initialization Pattern
<?php
/**
* Plugin Name: Featured Content Slider
* Description: Adds a featured content slider to your site
* Version: 1.0.0
*/
class Featured_Content_Slider {
// Singleton instance
private static $instance = null;
// Plugin settings
private $settings;
/**
* Constructor - sets up hooks and filters
*/
private function __construct() {
// Define plugin constants
$this->define_constants();
// Register activation/deactivation hooks
register_activation_hook(__FILE__, array($this, 'activate'));
register_deactivation_hook(__FILE__, array($this, 'deactivate'));
// Initialize settings
$this->settings = get_option('fcs_settings', array(
'slide_count' => 5,
'animation' => 'fade',
'autoplay' => true
));
// Add action hooks
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('init', array($this, 'register_slider_post_type'));
add_action('widgets_init', array($this, 'register_widgets'));
// Add shortcode
add_shortcode('featured_slider', array($this, 'render_slider'));
}
/**
* Singleton pattern - get instance
*/
public static function get_instance() {
if (null === self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Define plugin constants
*/
private function define_constants() {
define('FCS_VERSION', '1.0.0');
define('FCS_PLUGIN_DIR', plugin_dir_path(__FILE__));
define('FCS_PLUGIN_URL', plugin_dir_url(__FILE__));
}
/**
* Plugin activation hook
*/
public function activate() {
// Create default options
if (!get_option('fcs_settings')) {
add_option('fcs_settings', $this->settings);
}
// Create database tables if needed
// Flush rewrite rules
flush_rewrite_rules();
}
// Other methods would go here...
}
// Initialize the plugin
function featured_content_slider() {
return Featured_Content_Slider::get_instance();
}
// Kick off the plugin
featured_content_slider();
?>
In this example, the constructor serves as the central hub for initializing the entire plugin. It:
- Sets up constants for the plugin
- Registers activation and deactivation hooks
- Initializes plugin settings
- Registers WordPress hooks, filters, and shortcodes
Widget Class Constructor
<?php
class Featured_Posts_Widget extends WP_Widget {
/**
* Constructor
*/
public function __construct() {
// Call parent constructor
parent::__construct(
'featured_posts_widget', // Base ID
__('Featured Posts', 'textdomain'), // Name
array(
'description' => __('Displays featured posts with thumbnails', 'textdomain'),
'classname' => 'widget_featured_posts'
)
);
// Add any additional initialization
add_action('widgets_init', function() {
register_widget('Featured_Posts_Widget');
});
// Add widget-specific styles
add_action('wp_enqueue_scripts', array($this, 'enqueue_styles'));
}
/**
* Enqueue widget styles
*/
public function enqueue_styles() {
if (is_active_widget(false, false, $this->id_base, true)) {
wp_enqueue_style('featured-posts-widget', plugin_dir_url(__FILE__) . 'css/widget.css', array(), '1.0.0');
}
}
// Other widget methods (form, update, widget) would go here...
}
?>
In WordPress widgets, the constructor is used to:
- Define widget properties (name, ID, description)
- Call the parent constructor
- Register the widget with WordPress
- Set up any widget-specific assets or functionality
Theme Customizer Class Constructor
<?php
class Theme_Customizer {
/**
* Constructor
*/
public function __construct() {
// Register the customizer sections and settings
add_action('customize_register', array($this, 'register_customizer_sections'));
// Output custom CSS based on settings
add_action('wp_head', array($this, 'output_customizer_css'));
// Enqueue customizer preview scripts
add_action('customize_preview_init', array($this, 'enqueue_customizer_scripts'));
}
/**
* Register customizer sections, settings, and controls
*/
public function register_customizer_sections($wp_customize) {
// Add custom section
$wp_customize->add_section('theme_header_options', array(
'title' => __('Header Options', 'textdomain'),
'priority' => 100
));
// Add settings and controls
$wp_customize->add_setting('header_background_color', array(
'default' => '#ffffff',
'sanitize_callback' => 'sanitize_hex_color'
));
$wp_customize->add_control(new WP_Customize_Color_Control(
$wp_customize,
'header_background_color',
array(
'label' => __('Header Background Color', 'textdomain'),
'section' => 'theme_header_options',
'settings' => 'header_background_color'
)
));
}
// Other methods would go here...
}
// Initialize the customizer
new Theme_Customizer();
?>
In this theme customizer example, the constructor registers various WordPress hooks that will be used to integrate with the Customizer API.
Working with Constructor Parameters
Parameter Types and Default Values
<?php
class Product {
private $name;
private $price;
private $categories;
private $active;
private $created;
/**
* Product constructor with various parameter types
*
* @param string $name Product name
* @param float $price Product price
* @param array $categories Product categories
* @param bool $active Is product active?
* @param DateTime|null $created Creation date
*/
public function __construct(
string $name,
float $price,
array $categories = [],
bool $active = true,
?DateTime $created = null
) {
$this->name = $name;
$this->price = $price;
$this->categories = $categories;
$this->active = $active;
$this->created = $created ?: new DateTime();
}
// Getter methods
public function getName(): string {
return $this->name;
}
public function getPrice(): float {
return $this->price;
}
public function getCategories(): array {
return $this->categories;
}
public function isActive(): bool {
return $this->active;
}
public function getCreated(): DateTime {
return $this->created;
}
}
// Using the constructor
$product = new Product(
'WordPress Theme',
49.99,
['themes', 'premium', 'responsive'],
true,
new DateTime('2025-01-15')
);
// Using default values for optional parameters
$simpleProduct = new Product('WordPress Plugin', 29.99);
echo "Product: " . $product->getName() . "
";
echo "Price: $" . $product->getPrice() . "
";
echo "Categories: " . implode(', ', $product->getCategories()) . "
";
echo "Active: " . ($product->isActive() ? 'Yes' : 'No') . "
";
echo "Created: " . $product->getCreated()->format('Y-m-d') . "
";
?>
Modern PHP (7.0+) allows type hints and return type declarations, making your code more robust and self-documenting. This is especially valuable in larger WordPress projects.
Constructor Property Promotion (PHP 8.0+)
For WordPress developers using PHP 8.0+, there's a more concise way to define properties and initialize them via the constructor:
<?php
// PHP 8.0+ constructor property promotion
class Product {
/**
* Product constructor using property promotion
*/
public function __construct(
private string $name,
private float $price,
private array $categories = [],
private bool $active = true,
private ?DateTime $created = null
) {
// Properties are automatically created and assigned from parameters
$this->created ??= new DateTime();
}
// Getter methods
public function getName(): string {
return $this->name;
}
// Other methods...
}
// Usage is the same as before
$product = new Product('WordPress Theme', 49.99);
?>
This shorthand syntax eliminates repetitive property declarations and assignments in the constructor, resulting in cleaner, more maintainable code.
Advanced Constructor Patterns
Dependency Injection
A powerful pattern for making your WordPress classes more testable and modular:
<?php
class EmailService {
public function send($to, $subject, $message) {
// Email sending logic
return true;
}
}
class LogService {
public function log($message, $level = 'info') {
// Logging logic
return true;
}
}
class UserNotifier {
private $emailService;
private $logService;
/**
* Constructor with dependency injection
*/
public function __construct(EmailService $emailService, LogService $logService) {
$this->emailService = $emailService;
$this->logService = $logService;
}
/**
* Notify a user
*/
public function notifyUser($userId, $message) {
// Get user email (simplified example)
$userEmail = "user{$userId}@example.com";
// Send notification
$result = $this->emailService->send($userEmail, 'Notification', $message);
// Log the result
$this->logService->log("Notification sent to user {$userId}: " . ($result ? 'success' : 'failed'));
return $result;
}
}
// Usage with dependency injection
$emailService = new EmailService();
$logService = new LogService();
$notifier = new UserNotifier($emailService, $logService);
$notifier->notifyUser(123, 'Your account has been updated');
?>
By injecting dependencies through the constructor, your code becomes:
- More testable (you can inject mock services for testing)
- Loosely coupled (components can be swapped out)
- Clearer about its dependencies
WordPress-Specific Initialization
Some WordPress-specific patterns you might use in constructors:
<?php
class Advanced_Plugin {
private $options;
private $plugin_path;
private $plugin_url;
/**
* Constructor with WordPress-specific initialization
*/
public function __construct() {
// Get plugin information using WordPress functions
$this->plugin_path = plugin_dir_path(__FILE__);
$this->plugin_url = plugin_dir_url(__FILE__);
// Load options from WordPress database
$this->options = get_option('advanced_plugin_options', $this->get_default_options());
// Load translations
add_action('plugins_loaded', array($this, 'load_textdomain'));
// Register hooks based on environment
if (is_admin()) {
// Admin-specific hooks
add_action('admin_menu', array($this, 'add_admin_menu'));
add_action('admin_init', array($this, 'register_settings'));
} else {
// Frontend hooks
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
add_action('wp_footer', array($this, 'add_footer_content'));
}
// Common hooks
add_action('init', array($this, 'register_post_types'));
add_action('widgets_init', array($this, 'register_widgets'));
}
/**
* Get default plugin options
*/
private function get_default_options() {
return array(
'feature_x' => true,
'feature_y' => false,
'max_items' => 5
);
}
/**
* Load plugin text domain
*/
public function load_textdomain() {
load_plugin_textdomain('advanced-plugin', false, dirname(plugin_basename(__FILE__)) . '/languages/');
}
// Other methods would go here...
}
// Initialize the plugin
new Advanced_Plugin();
?>
This pattern demonstrates how constructors in WordPress plugins often handle:
- Setting up plugin paths and URLs
- Loading options from the WordPress database
- Registering different hooks for admin and frontend contexts
- Initializing internationalization
Destructors: The Cleanup Crew
What Are Destructors?
A destructor is a special method called when an object is no longer needed and is about to be destroyed. In PHP, destructors are defined using the __destruct() method name.
The Anatomy of a Destructor
<?php
class DatabaseConnection {
private $connection;
private $queries = [];
private $logger;
/**
* Constructor establishes connection
*/
public function __construct($host, $username, $password, $database) {
// For demonstration purposes, we'll just store connection info
$this->connection = "Connected to {$database} on {$host}";
$this->logger = fopen('database.log', 'a');
$this->log("Connection established");
echo "Database connection established
";
}
/**
* Execute a query
*/
public function query($sql) {
$this->queries[] = $sql;
$this->log("Executed: {$sql}");
return "Query result for: {$sql}";
}
/**
* Log a message
*/
private function log($message) {
if ($this->logger) {
fwrite($this->logger, date('[Y-m-d H:i:s] ') . $message . PHP_EOL);
}
}
/**
* Destructor closes connection and resources
*/
public function __destruct() {
// Close log file
if ($this->logger) {
$this->log("Connection closed after " . count($this->queries) . " queries");
fclose($this->logger);
}
// Close database connection
$this->connection = null;
echo "Database connection closed
";
}
}
// Create a scope block so we can see the destructor in action
{
echo "Creating database connection...
";
$db = new DatabaseConnection('localhost', 'username', 'password', 'wordpress');
// Run some queries
$db->query("SELECT * FROM wp_posts LIMIT 10");
$db->query("SELECT * FROM wp_users WHERE user_status = 1");
echo "Finished database operations
";
// The $db object will be destroyed when it goes out of scope
}
echo "After the database operations
";
?>
What Destructors Do
- Resource cleanup: Closing file handles, database connections
- Finalization: Completing operations or saving state
- Logging: Tracking object lifecycle events
- Housekeeping: Removing temporary files or data
Best Practice
Don't rely on destructors for critical operations. PHP's garbage collection is not guaranteed to run at a predictable time, especially in long-running WordPress applications (like with object caching or persistent PHP processes).
When Destructors Are Called
Destructors are called in several scenarios:
<?php
class DestructorDemo {
private $name;
public function __construct($name) {
$this->name = $name;
echo "{$this->name}: Object created
";
}
public function __destruct() {
echo "{$this->name}: Object destroyed
";
}
}
// Scenario 1: End of script
$obj1 = new DestructorDemo("Scenario 1");
// $obj1's destructor will be called at the end of the script
// Scenario 2: Object goes out of scope
function testScope() {
$obj2 = new DestructorDemo("Scenario 2");
echo "Inside function
";
// $obj2's destructor will be called when the function ends
}
testScope();
echo "After function
";
// Scenario 3: Object reassigned
$obj3 = new DestructorDemo("Scenario 3a");
$obj3 = new DestructorDemo("Scenario 3b");
// Scenario 3a's destructor is called when $obj3 is reassigned
// Scenario 4: Object explicitly destroyed
$obj4 = new DestructorDemo("Scenario 4");
unset($obj4);
// $obj4's destructor is called immediately
echo "End of script
";
// All remaining objects have their destructors called
?>
The output would show the order in which objects are created and destroyed, illustrating the various scenarios that trigger destructors.
Destructors in WordPress Development
While destructors are less commonly used in WordPress than constructors, they have valuable applications:
Logging and Debugging
<?php
class Performance_Logger {
private $start_time;
private $component_name;
private $actions = [];
/**
* Constructor starts the timer
*/
public function __construct($component_name) {
$this->start_time = microtime(true);
$this->component_name = $component_name;
$this->log("Started");
}
/**
* Log an action with timestamp
*/
public function log($action) {
$time = microtime(true);
$this->actions[] = [
'action' => $action,
'time' => $time,
'elapsed' => $time - $this->start_time
];
}
/**
* Destructor logs the total execution time
*/
public function __destruct() {
$end_time = microtime(true);
$total_time = $end_time - $this->start_time;
// Only log if WP_DEBUG is enabled
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log("PERFORMANCE [{$this->component_name}] Total execution time: {$total_time} seconds");
foreach ($this->actions as $action) {
error_log("PERFORMANCE [{$this->component_name}] {$action['action']} - {$action['elapsed']} seconds");
}
}
}
}
// Usage in a WordPress plugin function
function process_complex_shortcode($atts) {
$logger = new Performance_Logger('Complex Shortcode');
// Process attributes
$logger->log("Attributes processed");
// Query posts
$posts = get_posts(['numberposts' => 10]);
$logger->log("Posts queried: " . count($posts));
// Format output
$output = "";
foreach ($posts as $post) {
$output .= "{$post->post_title}
";
$output .= "{$post->post_excerpt}
";
}
$output .= "";
$logger->log("Output formatted");
// Destructor will automatically log the total time when function ends
return $output;
}
?>
This example uses a destructor to automatically log performance metrics when the function completes.
Resource Management
<?php
class Temporary_File_Manager {
private $temp_files = [];
/**
* Constructor
*/
public function __construct() {
// Nothing special to initialize
}
/**
* Create a temporary file
*/
public function create_temp_file($content, $prefix = 'wp_temp_') {
$temp_file = wp_tempnam($prefix);
file_put_contents($temp_file, $content);
$this->temp_files[] = $temp_file;
return $temp_file;
}
/**
* Manually clean up a specific file
*/
public function delete_temp_file($file_path) {
if (in_array($file_path, $this->temp_files)) {
@unlink($file_path);
$this->temp_files = array_diff($this->temp_files, [$file_path]);
return true;
}
return false;
}
/**
* Destructor cleans up any remaining temporary files
*/
public function __destruct() {
foreach ($this->temp_files as $file) {
if (file_exists($file)) {
@unlink($file);
}
}
}
}
// Usage in a WordPress function
function generate_pdf_report() {
$file_manager = new Temporary_File_Manager();
// Create temporary files for the report process
$data_file = $file_manager->create_temp_file(json_encode(['sales' => 1000, 'customers' => 50]), 'report_data_');
$config_file = $file_manager->create_temp_file('{"template": "sales", "format": "A4"}', 'report_config_');
// Process the files to generate PDF (simplified example)
$pdf_path = ABSPATH . 'wp-content/uploads/reports/report-' . date('Y-m-d') . '.pdf';
// Imagine PDF generation happens here using the temp files
// We can manually delete files we don't need anymore
$file_manager->delete_temp_file($data_file);
// Any remaining temp files will be automatically deleted by the destructor
return $pdf_path;
}
?>
This example demonstrates using a destructor as a safety net to clean up temporary files, ensuring they don't accumulate even if exceptions occur.
Parent Constructors and Inheritance
When working with inheritance, you often need to call the parent constructor. This is especially common in WordPress where many classes extend WordPress core classes.
<?php
/**
* Example of a WordPress widget with parent constructor
*/
class Latest_Products_Widget extends WP_Widget {
/**
* Constructor - must call parent constructor
*/
public function __construct() {
// Call parent constructor
parent::__construct(
'latest_products', // Base ID
__('Latest Products', 'textdomain'), // Name
[
'description' => __('Displays latest WooCommerce products', 'textdomain'),
'classname' => 'widget_latest_products'
]
);
// Add our own initialization
if (is_active_widget(false, false, $this->id_base, true)) {
add_action('wp_enqueue_scripts', [$this, 'enqueue_styles']);
}
}
/**
* Enqueue widget-specific styles
*/
public function enqueue_styles() {
wp_enqueue_style(
'latest-products-widget',
plugin_dir_url(__FILE__) . 'css/latest-products.css',
[],
'1.0.0'
);
}
/**
* Front-end display
*/
public function widget($args, $instance) {
// Extract widget arguments and instance settings
extract($args);
$title = apply_filters('widget_title', $instance['title'] ?? '');
$number = isset($instance['number']) ? absint($instance['number']) : 5;
// Start widget output
echo $before_widget;
if ($title) {
echo $before_title . $title . $after_title;
}
// Check if WooCommerce exists
if (class_exists('WooCommerce')) {
// Query products
$products = wc_get_products([
'status' => 'publish',
'limit' => $number,
'orderby' => 'date',
'order' => 'DESC'
]);
if ($products) {
echo '';
foreach ($products as $product) {
echo '- ';
echo '';
if ($product->get_image_id()) {
echo $product->get_image('thumbnail');
}
echo '' . $product->get_name() . '';
echo '' . $product->get_price_html() . '';
echo '';
echo '
';
}
echo '
';
} else {
echo '' . __('No products found.', 'textdomain') . '
';
}
} else {
echo '' . __('WooCommerce is not active.', 'textdomain') . '
';
}
echo $after_widget;
}
/**
* Backend widget form
*/
public function form($instance) {
$title = isset($instance['title']) ? $instance['title'] : __('Latest Products', 'textdomain');
$number = isset($instance['number']) ? absint($instance['number']) : 5;
?>
Key points about parent constructors:
- Use
parent::__construct()to call the parent class constructor - Make sure to pass all required parameters to the parent constructor
- Call the parent constructor before performing your own initialization
- This ensures the parent class is properly set up before you add your customizations
Common Constructor and Destructor Mistakes
Constructor Pitfalls
<?php
class Problematic_Constructor {
private $data;
private $api_url;
/**
* Problematic constructor with multiple issues
*/
public function __construct() {
// MISTAKE 1: Doing too much in the constructor
$this->data = $this->fetch_large_dataset(); // Slow API call
// MISTAKE 2: Not handling errors properly
$this->api_url = get_option('api_endpoint'); // What if this is empty?
// MISTAKE 3: Calling methods that might not be ready
add_action('init', [$this, 'register_post_type']); // Fine
$this->process_data(); // Might depend on WordPress being fully loaded
// MISTAKE 4: Hardcoding values that should be configurable
$this->timeout = 30; // Should be a parameter or setting
}
private function fetch_large_dataset() {
// Imagine this makes a slow API call
return ['item1', 'item2', /* many more items */];
}
private function process_data() {
// Processing that might depend on WordPress being fully initialized
$post_ids = get_posts(['fields' => 'ids']);
// Do something with the post IDs
}
public function register_post_type() {
// Register a custom post type
}
}
// BETTER APPROACH
class Improved_Constructor {
private $data;
private $api_url;
private $timeout;
/**
* Improved constructor with lazy loading and error handling
*/
public function __construct($timeout = 30) {
// Store configuration, but don't do heavy processing
$this->timeout = $timeout;
// Set up hooks
add_action('init', [$this, 'register_post_type']);
add_action('wp_loaded', [$this, 'initialize']);
}
/**
* Initialize after WordPress is fully loaded
*/
public function initialize() {
// Get API URL with fallback
$this->api_url = get_option('api_endpoint', 'https://default-api.example.com');
// We don't load data immediately, only when needed
}
/**
* Lazy load data only when needed
*/
public function get_data() {
if (null === $this->data) {
$this->data = $this->fetch_large_dataset();
}
return $this->data;
}
private function fetch_large_dataset() {
// API call with error handling
try {
// Fetch data with timeout parameter
return ['item1', 'item2', /* many more items */];
} catch (Exception $e) {
error_log('Failed to fetch dataset: ' . $e->getMessage());
return [];
}
}
}
?>
Destructor Pitfalls
<?php
class Problematic_Destructor {
private $important_file;
public function __construct() {
$this->important_file = tmpfile();
fwrite($this->important_file, "Important data");
}
// MISTAKE 1: Putting critical operations in destructor
public function __destruct() {
// Save critical data to database
update_option('important_data', fread($this->important_file, 1024));
// MISTAKE 2: No error handling
fclose($this->important_file);
// MISTAKE 3: Heavy processing in destructor
$this->process_and_log_statistics(); // Might be slow
}
private function process_and_log_statistics() {
// Imagine this does heavy processing
}
}
// BETTER APPROACH
class Improved_Destructor {
private $temp_file;
private $data_saved = false;
public function __construct() {
$this->temp_file = tmpfile();
fwrite($this->temp_file, "Important data");
}
/**
* Explicit method to save data
*/
public function save_data() {
try {
fseek($this->temp_file, 0);
update_option('important_data', fread($this->temp_file, 1024));
$this->data_saved = true;
return true;
} catch (Exception $e) {
error_log('Failed to save data: ' . $e->getMessage());
return false;
}
}
/**
* Process statistics explicitly
*/
public function process_statistics() {
// Processing logic here
}
/**
* Improved destructor focuses only on cleanup
*/
public function __destruct() {
// Only try to save data if not already saved
if (!$this->data_saved) {
try {
$this->save_data();
} catch (Exception $e) {
error_log('Failed to save data during destruction: ' . $e->getMessage());
}
}
// Always try to clean up resources
if (is_resource($this->temp_file)) {
@fclose($this->temp_file);
}
}
}
?>
Best Practices Summary
For constructors:
- Keep them light and fast (avoid heavy operations)
- Handle errors properly
- Use lazy loading for heavy resources
- Be mindful of WordPress initialization stages
- Use parameters for configuration instead of hardcoding
For destructors:
- Use them primarily for cleanup, not critical operations
- Include error handling
- Don't assume resources are still available
- Keep them lightweight
- Provide explicit methods for important operations
Parent Destructors and Inheritance
Just like with constructors, when extending a class, you might need to call the parent's destructor:
<?php
class Resource_Manager {
protected $resources = [];
public function __construct() {
echo "Resource_Manager: Initialized
";
}
public function add_resource($name, $value) {
$this->resources[$name] = $value;
echo "Resource_Manager: Added resource '{$name}'
";
}
public function __destruct() {
echo "Resource_Manager: Cleaning up " . count($this->resources) . " resources
";
$this->resources = [];
}
}
class File_Resource_Manager extends Resource_Manager {
private $open_files = [];
public function __construct() {
parent::__construct();
echo "File_Resource_Manager: Initialized
";
}
public function open_file($path, $mode = 'r') {
$handle = fopen($path, $mode);
$this->open_files[] = $handle;
$this->add_resource('file_' . count($this->open_files), $path);
return $handle;
}
public function __destruct() {
echo "File_Resource_Manager: Closing " . count($this->open_files) . " files
";
foreach ($this->open_files as $handle) {
if (is_resource($handle)) {
fclose($handle);
}
}
parent::__destruct();
echo "File_Resource_Manager: Destruction complete
";
}
}
// Usage
$file_manager = new File_Resource_Manager();
$temp_file = tempnam(sys_get_temp_dir(), 'wp_');
$handle = $file_manager->open_file($temp_file, 'w');
fwrite($handle, "Hello World");
echo "Main operation complete
";
// $file_manager will be destroyed at end of script
?>
When extending classes with destructors:
- Call
parent::__destruct()to ensure parent cleanup runs - Consider whether to run your cleanup before or after the parent's
- The parent destructor is not called automatically (unlike constructors)
Practical Exercise: WordPress Theme Options Class
Let's create a complete class that uses constructors and destructors in a practical WordPress context:
<?php
/**
* Theme Options Manager Class
*
* Demonstrates proper use of constructors and destructors for
* managing theme options in WordPress.
*/
class Theme_Options_Manager {
// Default options
private $defaults = [
'color_scheme' => 'light',
'font_size' => 'medium',
'show_sidebar' => true,
'header_layout' => 'standard'
];
// Current options
private $options = [];
// Has the options been modified?
private $is_modified = false;
// Cache file for options
private $cache_file;
/**
* Constructor: Initialize the options manager
*
* @param array $custom_defaults Optional custom defaults to override standard ones
*/
public function __construct($custom_defaults = []) {
// Set up the cache directory
$upload_dir = wp_upload_dir();
$cache_dir = $upload_dir['basedir'] . '/theme-cache';
// Create cache directory if it doesn't exist
if (!file_exists($cache_dir)) {
wp_mkdir_p($cache_dir);
}
$this->cache_file = $cache_dir . '/theme-options.json';
// Merge custom defaults with standard defaults
if (!empty($custom_defaults) && is_array($custom_defaults)) {
$this->defaults = array_merge($this->defaults, $custom_defaults);
}
// Try to load from cache first (faster)
$this->load_from_cache();
// If cache failed, load from database
if (empty($this->options)) {
$this->load_from_database();
}
// If still empty, use defaults
if (empty($this->options)) {
$this->options = $this->defaults;
}
// Register hooks
add_action('admin_menu', [$this, 'add_options_page']);
add_action('admin_init', [$this, 'register_settings']);
}
/**
* Load options from cache file
*/
private function load_from_cache() {
if (file_exists($this->cache_file)) {
$json = file_get_contents($this->cache_file);
$cached_options = json_decode($json, true);
if (is_array($cached_options)) {
$this->options = $cached_options;
return true;
}
}
return false;
}
/**
* Load options from WordPress database
*/
private function load_from_database() {
$saved_options = get_option('theme_options');
if (is_array($saved_options)) {
$this->options = array_merge($this->defaults, $saved_options);
// Update cache while we're at it
$this->update_cache();
return true;
}
return false;
}
/**
* Update the cache file
*/
private function update_cache() {
$json = json_encode($this->options);
@file_put_contents($this->cache_file, $json);
}
/**
* Get an option value
*
* @param string $key Option key
* @param mixed $default Default value if option doesn't exist
* @return mixed Option value
*/
public function get($key, $default = null) {
if (isset($this->options[$key])) {
return $this->options[$key];
}
if ($default !== null) {
return $default;
}
return isset($this->defaults[$key]) ? $this->defaults[$key] : null;
}
/**
* Set an option value
*
* @param string $key Option key
* @param mixed $value Option value
* @return bool Success
*/
public function set($key, $value) {
// Check if the value is actually different
if (!isset($this->options[$key]) || $this->options[$key] !== $value) {
$this->options[$key] = $value;
$this->is_modified = true;
return true;
}
return false;
}
/**
* Save options to database
*
* @return bool Success
*/
public function save() {
if ($this->is_modified) {
$result = update_option('theme_options', $this->options);
if ($result) {
$this->update_cache();
$this->is_modified = false;
}
return $result;
}
return true; // Nothing to save
}
/**
* Reset options to defaults
*
* @return bool Success
*/
public function reset() {
$this->options = $this->defaults;
$this->is_modified = true;
return $this->save();
}
/**
* Add theme options page to admin menu
*/
public function add_options_page() {
add_theme_page(
__('Theme Options', 'textdomain'),
__('Theme Options', 'textdomain'),
'edit_theme_options',
'theme-options',
[$this, 'render_options_page']
);
}
/**
* Register settings for the options page
*/
public function register_settings() {
register_setting('theme_options_group', 'theme_options');
add_settings_section(
'theme_options_section',
__('Theme Options', 'textdomain'),
[$this, 'render_section_intro'],
'theme-options'
);
// Register fields for each option
foreach ($this->defaults as $key => $value) {
add_settings_field(
'theme_option_' . $key,
ucwords(str_replace('_', ' ', $key)),
[$this, 'render_field'],
'theme-options',
'theme_options_section',
['key' => $key]
);
}
}
/**
* Render the intro text for the options section
*/
public function render_section_intro() {
echo '' . __('Customize your theme settings below.', 'textdomain') . '
';
}
/**
* Render a field based on its type
*/
public function render_field($args) {
$key = $args['key'];
$value = $this->get($key);
$name = "theme_options[$key]";
switch ($key) {
case 'color_scheme':
echo '';
break;
case 'font_size':
echo '';
break;
case 'show_sidebar':
echo '';
break;
case 'header_layout':
echo '';
break;
default:
echo '';
}
}
/**
* Render the options page
*/
public function render_options_page() {
?>
reset();
wp_redirect(admin_url('themes.php?page=theme-options&reset=success'));
exit;
}
}
/**
* Destructor: Save any unsaved changes
*/
public function __destruct() {
// Automatically save any modified options when the object is destroyed
if ($this->is_modified) {
$this->save();
}
}
}
// Usage example
function mytheme_get_options_manager() {
static $options_manager = null;
if (null === $options_manager) {
$options_manager = new Theme_Options_Manager();
}
return $options_manager;
}
// Get an option
function mytheme_get_option($key, $default = null) {
return mytheme_get_options_manager()->get($key, $default);
}
// Set an option
function mytheme_set_option($key, $value) {
return mytheme_get_options_manager()->set($key, $value);
}
// Save options immediately (not waiting for destructor)
function mytheme_save_options() {
return mytheme_get_options_manager()->save();
}
?>
This example demonstrates:
- A robust constructor that initializes options from multiple sources (cache, database, defaults)
- A destructor that automatically saves changes when the object is destroyed
- Proper integration with the WordPress admin interface
- Caching for performance optimization
- Helper functions to make accessing the class easier from anywhere in the theme
Homework: Create a Simple Class with Constructor and Destructor
Now it's your turn to practice with constructors and destructors:
Assignment
Create a File_Logger class that could be used to log events in a WordPress plugin or theme. The class should:
- Have a constructor that initializes the log file and starting timestamp
- Include methods for logging different types of events (info, warning, error)
- Have a destructor that closes the file properly and logs the end timestamp
- Handle potential errors gracefully
Here's a starter template to help you:
<?php
/**
* File Logger Class for WordPress
*
* A simple class to log events to a file with timestamps.
*/
class File_Logger {
// Properties for file handle, log path, etc.
private $file_handle;
private $log_path;
private $start_time;
/**
* Constructor
*
* @param string $log_name Name of the log file (default: 'debug')
*/
public function __construct($log_name = 'debug') {
// Initialize the log file
// Set the start time
// Create the file handle
}
/**
* Log an informational message
*
* @param string $message The message to log
*/
public function info($message) {
// Log with 'INFO' level
}
/**
* Log a warning message
*
* @param string $message The message to log
*/
public function warning($message) {
// Log with 'WARNING' level
}
/**
* Log an error message
*
* @param string $message The message to log
*/
public function error($message) {
// Log with 'ERROR' level
}
/**
* Internal method to write to the log file
*
* @param string $level The log level
* @param string $message The message to log
*/
private function write_log($level, $message) {
// Format the log entry
// Write to the file
}
/**
* Destructor
*/
public function __destruct() {
// Log the end timestamp
// Close the file handle properly
}
}
// Example usage:
function example_plugin_process() {
$logger = new File_Logger('my_plugin');
$logger->info('Processing started');
try {
// Some plugin operation
$logger->info('Operation successful');
} catch (Exception $e) {
$logger->error('Error: ' . $e->getMessage());
}
// Logger will automatically close the file when the function ends
}
?>
Complete this class with your own implementation. Consider how you would handle file permissions, directory creation, and error scenarios.
Additional Resources
Further Reading
- PHP Manual: Constructors and Destructors
- WordPress Plugin Developer Handbook: Best Practices
- PHP The Right Way: OOP Chapter
Next Class Preview
In our next session, we'll explore access modifiers (public, private, protected) and how they control visibility and encapsulation in your classes. We'll also learn about the $this keyword and how it's used to reference the current object instance.