-
Identify the event name and handler method name.
- For module-scoped events: key =
self::$module.'.your_event' → method name e.g. handleYourEvent.
- For global events: key =
'global.event_name' → method name e.g. onGlobalEventName.
- Verify the event name follows the dot-separated convention used by existing hooks in
src/Plugin.php (e.g., load_processing, settings).
-
Register the hook in getHooks() in src/Plugin.php.
Add the new entry to the returned array. This step uses the names determined in Step 1.
public static function getHooks()
{
return [
'api.register' => [__CLASS__, 'apiRegister'],
'function.requirements' => [__CLASS__, 'getRequirements'],
self::$module.'.load_processing' => [__CLASS__, 'loadProcessing'],
self::$module.'.settings' => [__CLASS__, 'getSettings'],
self::$module.'.your_event' => [__CLASS__, 'handleYourEvent'],
];
}
Verify: the new key string appears in the array and __CLASS__ is used (not the literal class name).
-
Implement the handler method in src/Plugin.php.
Add the method after the last existing handler, before the closing } of the class.
Always start by extracting the subject from the event:
public static function handleYourEvent(GenericEvent $event)
{
$subject = $event->getSubject();
$settings = get_module_settings(self::$module);
$db = get_module_db(self::$module);
}
Verify: method is public static, parameter is typed GenericEvent $event, no return statement.
-
Add a DB query if the handler reads/writes service data (optional).
Always pass __LINE__, __FILE__ and escape user input:
$db->query(
"SELECT * FROM {$settings['TABLE']} WHERE {$settings['PREFIX']}_id='" . $db->real_escape($id) . "'",
__LINE__, __FILE__
);
$db->next_record(MYSQL_ASSOC);
$row = $db->Record;
Never use PDO. Never interpolate $_GET/$_POST directly — always $db->real_escape() first.
-
Add a PHPUnit test in tests/PluginTest.php.
Follow the pattern of testGetSettingsHookMapping() and testGetSettingsMethodSignature():
public function testHandleYourEventHookMapping(): void
{
$hooks = Plugin::getHooks();
$this->assertSame([Plugin::class, 'handleYourEvent'], $hooks[Plugin::$module . '.your_event']);
}
public function testHandleYourEventMethodSignature(): void
{
$method = (new ReflectionClass(Plugin::class))->getMethod('handleYourEvent');
$this->assertTrue($method->isStatic());
$this->assertTrue($method->isPublic());
$params = $method->getParameters();
$this->assertCount(1, $params);
$this->assertSame('event', $params[0]->getName());
$this->assertSame(GenericEvent::class, $params[0]->getType()->getName());
}
Verify: run vendor/bin/phpunit tests/PluginTest.php — all tests pass.
-
Run the full test suite.
composer test
All tests must pass before considering the hook complete.
-
Call to undefined method at runtime: The method name in getHooks() doesn't match the actual method name — check spelling exactly. Both must match (PHP method names are case-insensitive but keep them consistent).
-
Test testGetHooksReturnsFourHooks fails after adding hook: That test asserts exactly 4 hooks. Update it to the new count (assertCount(5, $hooks)) or replace it with a assertGreaterThanOrEqual check.
-
$event->getSubject() returns unexpected type: The subject type depends on which system dispatches the event. Check what run_event('licenses.your_event', $data, 'licenses') passes as the subject in the caller — loadProcessing receives a \ServiceHandler, getSettings receives \MyAdmin\Settings.
-
Hook never fires: Confirm the event name string exactly matches what the dispatcher uses. Module-specific hooks must be licenses.your_event (using the value of $module = 'licenses'), not license.your_event.
-
composer test reports parse error after edit: Trailing comma missing after new hook entry in the array — PHP 7.4+ allows trailing commas in arrays; add one after the last entry to avoid merge conflicts later.