OOP Foundation: Main Class & Autoloading
Setting Up Composer
Composer handles autoloading so you never write require statements manually. Navigate to your plugin folder and initialize:
cd wp-content/plugins/task-manager
composer initAnswer the prompts:
- Package name:
yourname/task-manager - Type:
wordpress-plugin - License:
GPL-2.0-or-later
Configuring PSR-4 Autoloading
Edit composer.json to add autoloading:
{
"name": "yourname/task-manager",
"description": "A modern task management plugin",
"type": "wordpress-plugin",
"license": "GPL-2.0-or-later",
"autoload": {
"psr-4": {
"TaskManager\": "src/"
}
},
"require": {
"php": ">=8.0"
}
}Generate the autoloader:
composer dump-autoloadThis creates vendor/autoload.php which loads any class in the TaskManager namespace from the src/ directory.
TaskManagerAdminSettings โ src/Admin/Settings.phpTaskManagerDatabaseTasksTable โ src/Database/TasksTable.php
The Singleton Pattern
For our main Plugin class, we'll use the Singleton pattern to ensure only one instance exists:
<?php
// src/Plugin.php
namespace TaskManager;
class Plugin {
/**
* Single instance of the plugin.
*/
private static ?Plugin $instance = null;
/**
* Plugin version.
*/
public const VERSION = '1.0.0';
/**
* Get plugin instance.
*/
public static function get_instance(): Plugin {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Private constructor - use get_instance().
*/
private function __construct() {
$this->init_hooks();
}
/**
* Prevent cloning.
*/
private function __clone() {}
/**
* Prevent unserialization.
*/
public function __wakeup() {
throw new Exception( 'Cannot unserialize singleton' );
}
/**
* Initialize WordPress hooks.
*/
private function init_hooks(): void {
// Admin hooks
if ( is_admin() ) {
add_action( 'admin_menu', [ $this, 'register_admin_menu' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_assets' ] );
}
// REST API
add_action( 'rest_api_init', [ $this, 'register_rest_routes' ] );
// Load translations
add_action( 'init', [ $this, 'load_textdomain' ] );
}
/**
* Register admin menu pages.
*/
public function register_admin_menu(): void {
$admin_menu = new AdminAdminMenu();
$admin_menu->register();
}
/**
* Enqueue admin CSS and JS.
*/
public function enqueue_admin_assets( string $hook ): void {
// Only load on our plugin pages
if ( ! str_contains( $hook, 'task-manager' ) ) {
return;
}
wp_enqueue_style(
'task-manager-admin',
TM_PLUGIN_URL . 'assets/css/admin.css',
[],
self::VERSION
);
wp_enqueue_script(
'task-manager-admin',
TM_PLUGIN_URL . 'assets/js/admin.js',
[ 'jquery' ],
self::VERSION,
true
);
}
/**
* Register REST API routes.
*/
public function register_rest_routes(): void {
$controller = new RESTTasksController();
$controller->register_routes();
}
/**
* Load plugin translations.
*/
public function load_textdomain(): void {
load_plugin_textdomain(
'task-manager',
false,
dirname( plugin_basename( TM_PLUGIN_FILE ) ) . '/languages'
);
}
}Activation & Deactivation Classes
Create separate classes for activation/deactivation logic:
<?php
// src/Activator.php
namespace TaskManager;
class Activator {
/**
* Run on plugin activation.
*/
public static function activate(): void {
// Create database tables
DatabaseTasksTable::create_table();
// Set default options
if ( false === get_option( 'task_manager_settings' ) ) {
update_option( 'task_manager_settings', [
'tasks_per_page' => 20,
'enable_api' => true,
]);
}
// Store version for upgrades
update_option( 'task_manager_version', Plugin::VERSION );
// Flush rewrite rules for custom endpoints
flush_rewrite_rules();
}
}<?php
// src/Deactivator.php
namespace TaskManager;
class Deactivator {
/**
* Run on plugin deactivation.
*/
public static function deactivate(): void {
// Clear scheduled events
wp_clear_scheduled_hook( 'task_manager_daily_cleanup' );
// Flush rewrite rules
flush_rewrite_rules();
}
}uninstall.php.
The Uninstall File
For complete cleanup when the plugin is deleted:
<?php
// uninstall.php
// Exit if not called by WordPress
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
exit;
}
global $wpdb;
// Delete options
delete_option( 'task_manager_settings' );
delete_option( 'task_manager_version' );
// Drop custom table
$table_name = $wpdb->prefix . 'task_manager_tasks';
$wpdb->query( "DROP TABLE IF EXISTS {$table_name}" );
// Clear any transients
delete_transient( 'task_manager_cache' );Dependency Injection (Advanced)
For larger plugins, use dependency injection instead of static calls:
<?php
// src/Container.php
namespace TaskManager;
class Container {
private array $services = [];
public function register( string $key, callable $factory ): void {
$this->services[ $key ] = $factory;
}
public function get( string $key ): mixed {
if ( ! isset( $this->services[ $key ] ) ) {
throw new Exception( "Service not found: {$key}" );
}
return $this->services[ $key ]( $this );
}
}
// Usage in Plugin class
private function register_services(): void {
$this->container = new Container();
$this->container->register( 'database', function() {
return new DatabaseTasksTable();
});
$this->container->register( 'admin', function( $c ) {
return new AdminAdminMenu( $c->get( 'database' ) );
});
}Testing Your Setup
Verify everything works:
- Activate the plugin in WordPress admin
- Check for PHP errors in
wp-content/debug.log - Confirm the menu item appears (we'll create it next lesson)
// Quick test - add to Plugin::init_hooks() temporarily
add_action( 'admin_notices', function() {
echo '<div class="notice notice-success"><p>Task Manager loaded!</p></div>';
});Next Steps
In Lesson 3, we'll dive deep into WordPress hooks โ actions and filters โ and learn how to create custom hooks for extensibility.