Overview
Plugins in PocketMine-MP go through a well-defined lifecycle managed by the PluginManager. Understanding this lifecycle is crucial for proper resource initialization and cleanup.
Lifecycle Phases
Phase 1: Plugin Discovery
The PluginManager scans the plugins directory for valid plugins.
// plugins/
// MyPlugin.phar
// AnotherPlugin.phar
// MyPlugin/ (folder plugins)
PHAR archives (.phar) - Loaded by PharPluginLoader
Script plugins (.php) - Loaded by ScriptPluginLoader
Phase 2: Loading
Constructor
When a plugin is loaded, its constructor is called automatically:
public function __construct (
private PluginLoader $loader ,
private Server $server ,
private PluginDescription $description ,
private string $dataFolder ,
private string $file ,
private ResourceProvider $resourceProvider
) {
$this -> dataFolder = rtrim ( $dataFolder , "/" . DIRECTORY_SEPARATOR ) . "/" ;
$this -> configFile = Path :: join ( $this -> dataFolder , "config.yml" );
$prefix = $this -> description -> getPrefix ();
$this -> logger = new PluginLogger ( $server -> getLogger (), $prefix !== "" ? $prefix : $this -> getName ());
$this -> scheduler = new TaskScheduler ( $this -> getFullName ());
$this -> onLoad ();
$this -> registerYamlCommands ();
}
Never override the constructor. Use onLoad() instead for initialization logic.
onLoad() Method
Called during construction, before the plugin is enabled:
protected function onLoad () : void {
// Override this method for early initialization
}
Use Cases:
Register custom generators
Initialize static resources
Set up data structures
Register custom serializers
use pocketmine\plugin\ PluginBase ;
use pocketmine\world\generator\ GeneratorManager ;
class MyPlugin extends PluginBase {
protected function onLoad () : void {
// Register custom world generator
GeneratorManager :: getInstance () -> addGenerator (
CustomGenerator :: class ,
"custom" ,
fn () => null
);
$this -> getLogger () -> info ( "Custom generator registered" );
}
}
At this point, other plugins may not be loaded yet. Avoid accessing other plugins in onLoad().
Phase 3: Dependency Resolution
The PluginManager resolves dependencies and determines load order.
plugin.yml Dependencies
name : MyPlugin
version : 1.0.0
main : MyNamespace\MyPlugin
api : 5.0.0
# Hard dependencies (required)
depend :
- EssentialPlugin
- DatabaseLib
# Soft dependencies (optional, load before if present)
softdepend :
- OptionalPlugin
# Load order
load : POSTWORLD # or STARTUP
Load Orders
class PluginEnableOrder {
public const STARTUP = 0 ; // Before worlds are loaded
public const POSTWORLD = 1 ; // After worlds are loaded (default)
}
Phase 4: Enabling
onEnable() Method
Called when the plugin is enabled and ready for use:
protected function onEnable () : void {
// Override this method for main initialization
}
This is where you should:
Register Event Listeners $this -> getServer () -> getPluginManager ()
-> registerEvents ( new MyListener (), $this );
Schedule Tasks $this -> getScheduler () -> scheduleRepeatingTask (
new MyTask (), 20
);
Load Configuration $this -> saveDefaultConfig ();
$config = $this -> getConfig ();
Initialize Services $this -> database = new Database ( $config );
$this -> api = new APIClient ( $config );
Complete Example
use pocketmine\plugin\ PluginBase ;
use pocketmine\event\ Listener ;
class MyPlugin extends PluginBase {
private Database $database ;
protected function onLoad () : void {
// Early initialization
$this -> getLogger () -> info ( "Loading plugin data..." );
}
protected function onEnable () : void {
// Save default config if not exists
$this -> saveDefaultConfig ();
// Initialize database
$this -> database = new Database (
$this -> getConfig () -> get ( "database" )
);
// Register events
$this -> getServer () -> getPluginManager () -> registerEvents (
new MyListener ( $this ),
$this
);
// Register commands
$cmd = new MyCommand ( $this );
$this -> getServer () -> getCommandMap () -> register (
"myplugin" ,
$cmd
);
// Schedule repeating task (every 1 second)
$this -> getScheduler () -> scheduleRepeatingTask (
new AutoSaveTask ( $this ),
20
);
$this -> getLogger () -> info ( "Plugin enabled successfully!" );
}
protected function onDisable () : void {
// Cleanup resources
$this -> database -> close ();
$this -> getLogger () -> info ( "Plugin disabled" );
}
public function getDatabase () : Database {
return $this -> database ;
}
}
Phase 5: Running
While enabled, the plugin is fully operational:
$plugin -> isEnabled (); // true
$plugin -> isDisabled (); // false
Accessing Plugin State
// Get plugin instance
$myPlugin = $server -> getPluginManager () -> getPlugin ( "MyPlugin" );
if ( $myPlugin !== null && $myPlugin -> isEnabled ()) {
// Safe to use plugin
$myPlugin -> doSomething ();
}
Phase 6: Disabling
onDisable() Method
Called when the plugin is being disabled:
protected function onDisable () : void {
// Override this method for cleanup
}
You should:
Close database connections
Cancel running async tasks
Save pending data
Unregister custom resources
Free memory-intensive objects
protected function onDisable () : void {
// Save all pending data
$this -> dataManager -> saveAll ();
// Close connections
$this -> database -> close ();
$this -> redisClient -> disconnect ();
// Cancel tasks (automatic, but explicit is fine)
$this -> getScheduler () -> cancelAllTasks ();
$this -> getLogger () -> info ( "All resources freed" );
}
Event listeners and scheduled tasks are automatically unregistered when a plugin is disabled.
Automatic Lifecycle Management
What Gets Cleaned Up Automatically
final public function onEnableStateChange ( bool $enabled ) : void {
if ( $this -> isEnabled !== $enabled ) {
$this -> isEnabled = $enabled ;
if ( $this -> isEnabled ) {
$this -> onEnable ();
} else {
$this -> onDisable ();
}
}
}
When disabled, the PluginManager automatically:
Unregisters all event listeners
Cancels all scheduled tasks
Unregisters commands
Clears handler lists
Manual Cleanup Still Required
Database connections
File handles
Network sockets
External API connections
Custom threads
Plugin Reloading
PocketMine-MP does not support plugin reloading. Plugins must be disabled and the server restarted to reload code changes.
Why reloading isn’t supported:
PHP doesn’t support unloading classes
Static variables persist
Memory leaks from circular references
Event handler duplicates
Best Practices
Use onLoad() for registration, onEnable() for initialization: protected function onLoad () : void {
GeneratorManager :: getInstance () -> addGenerator ( ... );
}
protected function onEnable () : void {
$this -> getServer () -> getPluginManager () -> registerEvents ( ... );
}
Verify soft dependencies are available: protected function onEnable () : void {
$pm = $this -> getServer () -> getPluginManager ();
$economy = $pm -> getPlugin ( "EconomyAPI" );
if ( $economy === null ) {
$this -> getLogger () -> warning ( "EconomyAPI not found, some features disabled" );
$this -> economyEnabled = false ;
}
}
Handle missing resources gracefully: protected function onEnable () : void {
if ( ! $this -> saveDefaultConfig ()) {
$this -> getLogger () -> error ( "Failed to save config, using defaults" );
}
}
Implement onDisable() to prevent resource leaks: protected function onDisable () : void {
foreach ( $this -> connections as $conn ) {
$conn -> close ();
}
}
use pocketmine\event\plugin\ PluginEnableEvent ;
use pocketmine\event\plugin\ PluginDisableEvent ;
class MyListener implements Listener {
public function onPluginEnable ( PluginEnableEvent $event ) : void {
$plugin = $event -> getPlugin ();
$this -> getLogger () -> info ( $plugin -> getName () . " was enabled" );
}
public function onPluginDisable ( PluginDisableEvent $event ) : void {
$plugin = $event -> getPlugin ();
$this -> getLogger () -> info ( $plugin -> getName () . " was disabled" );
}
}