name: plugin-hook description: Adds a new event hook to src/Plugin.php — registers in getHooks() and creates the static GenericEvent handler. Use when user says 'add hook', 'handle event', 'new plugin event', or adds a lifecycle action (activate/deactivate/change IP/reactivate). Do NOT use for modifying existing handlers or adding inc functions.
plugin-hook
Critical
- All handler methods MUST be
public staticand accept exactly oneGenericEvent $eventparameter —testHookMethodsArePublicAndStaticandtestEventHandlerMethodSignatureswill fail otherwise. - Always guard the handler body with
if ($event['category'] == get_service_define('PARALLELS'))before any logic. - Always call
$event->stopPropagation()as the last statement inside the category guard. - Hook keys use
self::$module . '.event_name'(e.g.'licenses.change_ip') — never hardcode the module string. - The one exception is
'function.requirements'which uses a literal key, not the module prefix. - Run
composer testafter every change — a missing method or wrong signature will breaktestAllHookMethodsExistandtestClassMethodCount.
Instructions
-
Decide the event name and handler name. Event name is snake_case (e.g.
change_ip). Handler is PascalCase prefixed withget(e.g.getChangeIp). Verify no existing entry ingetHooks()already covers this event. -
Register the hook in
getHooks()insidesrc/Plugin.php. Add one line to the returned array:self::$module.'.change_ip' => [__CLASS__, 'getChangeIp'],If the new event should share an existing handler (like
reactivate→getActivate), point to the existing method name instead of creating a new one. Verify the array key is unique before proceeding. -
Add the handler method to
src/Plugin.phpafter the last existing handler, before the closing}:/** * @param \Symfony\Component\EventDispatcher\GenericEvent $event */ public static function getChangeIp(GenericEvent $event) { if ($event['category'] == get_service_define('PARALLELS')) { $serviceClass = $event->getSubject(); myadmin_log(self::$module, 'info', 'Parallels <Action Description>', __LINE__, __FILE__, self::$module, $serviceClass->getId()); // ... call function_requirements() then the inc function ... $event->stopPropagation(); } }The
use Symfony\Component\EventDispatcher\GenericEvent;import is already present at the top — do not add it again. -
If the handler calls an inc function, add
function_requirements('your_function')before the call and register the function path ingetRequirements():$loader->add_requirement('your_function', '/../src/parallels.inc.php');Verify the function exists in
src/parallels.inc.phpbefore registering. -
Update
tests/PluginTest.php— add the new hook key to the$expectedKeysarray intestGetHooksContainsExpectedKeys()and bump the count intestClassMethodCount()if a new method was added. Runcomposer testto confirm all tests pass.
Examples
User says: "Add a hook for licenses.change_ip that logs the old and new IP"
Actions taken:
getHooks() addition in src/Plugin.php:
self::$module.'.change_ip' => [__CLASS__, 'getChangeIp'],
New handler method in src/Plugin.php:
public static function getChangeIp(GenericEvent $event)
{
if ($event['category'] == get_service_define('PARALLELS')) {
$serviceClass = $event->getSubject();
myadmin_log(self::$module, 'info', 'IP Change - (OLD:'.$serviceClass->getIp().") (NEW:{$event['newip']})", __LINE__, __FILE__, self::$module, $serviceClass->getId());
// perform IP change logic here
$event['status'] = 'ok';
$event['status_text'] = 'The IP Address has been changed.';
$event->stopPropagation();
}
}
Result: composer test passes; testGetHooksContainsExpectedKeys and testAllHookMethodsExist both green.
Common Issues
testClassMethodCountfails with "Expected 7, got 8": You added a new handler but did not update the count assertion intests/PluginTest.php. Change theassertCount(7, ...)to the new total.testAllHookMethodsExistfails: The method name string ingetHooks()does not exactly match the declared method name. Check spelling and PascalCase —getChangeIp≠getChangeIP.testEventHandlerMethodSignaturesfails with "parameter should be GenericEvent": The handler is missing theGenericEvent $eventtype hint, or theusestatement is absent (it lives at the top ofsrc/Plugin.php— do not remove it).stopPropagationnot called: Event will continue dispatching to other plugins and produce duplicate side-effects. Always place$event->stopPropagation()as the final statement inside the category guard.function_requirementsnot registered: Ifactivate_parallelsor a new inc function is called but not registered ingetRequirements(), it will throw a fatal error at runtime. Verify$loader->add_requirement('fn_name', '...')is present.
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.