{"record":{"id":"ef63a4857856e71d","repo":"octobercms/october","slug":"handler-does-not-exist","errorCode":null,"errorMessage":"Handler does not exist","messagePattern":"Handler does not exist","errorType":"exception","errorClass":"SystemException","httpStatus":null,"severity":"error","filePath":"modules/dashboard/classes/VueReportWidgetBase.php","lineNumber":73,"sourceCode":"        $this->controller->registerVueComponent($this::class);\n    }\n\n    /**\n     * getData\n     */\n    abstract public function getData(ReportFetchData $data): mixed;\n\n    /**\n     * runHandler\n     */\n    public function runHandler(array $widgetConfig, string $handlerName, array $extraData): mixed\n    {\n        if (!preg_match('/^on[a-z0-9_]+/i', $handlerName)) {\n            throw new SystemException('Invalid handler name');\n        }\n\n        if (!method_exists($this, $handlerName)) {\n            throw new SystemException('Handler does not exist');\n        }\n\n        return $this->{$handlerName}($widgetConfig, $extraData);\n    }\n}\n","sourceCodeStart":55,"sourceCodeEnd":79,"githubUrl":"https://github.com/octobercms/october/blob/b608633a7e8922487d91a8161499020121c3b3bf/modules/dashboard/classes/VueReportWidgetBase.php#L55-L79","documentation":"The second guard in VueReportWidgetBase::runHandler(): after the name passes the on* regex, method_exists($this, $handlerName) must find a public method on the widget class, otherwise SystemException('Handler does not exist') is thrown. This means the name is well-formed but the widget class defines no such method — the handler was renamed, lives on a different class, or the wrong widget class is handling the request.","triggerScenarios":"Front-end still calls 'onRefreshData' after the method was renamed to 'onLoadData'; the AJAX route resolves to a different VueReportWidget subclass than the one containing the handler (wrong widget alias/class in the request); method defined but on a trait not used by the class, or defined as private/protected in a scope method_exists still finds but invocation may fail — check visibility too.","commonSituations":"Renaming a handler in PHP without updating the JS call site; duplicating a widget and forgetting to port its handlers; plugin version skew between front-end assets (cached JS) and back-end code.","solutions":["Add or restore the public method with the exact requested name on the widget class: public function onLoadData(array $widgetConfig, array $extraData): mixed.","Update the JS/front-end call to the current method name, and bust cached front-end assets after deploys.","Verify the request is routed to the widget class that actually defines the handler (check the widget alias/class in the AJAX payload).","If the handler moved, keep a deprecated onOldName() wrapper delegating to the new one during transition."],"exampleFix":"// before\n// JS: this.requestHandler('onLoadData') but the class only has onRefreshData()\n\n// after\npublic function onLoadData(array $widgetConfig, array $extraData): mixed\n{\n    return $this->onRefreshData($widgetConfig, $extraData);\n}","handlingStrategy":"type-guard","validationCode":"$handler = (string) $request->input('handler', '');\nif (!preg_match('/^on[a-z0-9_]+$/i', $handler) || !method_exists($widget, $handler)) {\n    throw new ApplicationException(\"Unknown handler '{$handler}' for widget \" . get_class($widget));\n}\n$widget->runHandler($config, $handler, $extra);","typeGuard":"function widgetHasHandler(VueReportWidgetBase $widget, string $name): bool\n{\n    return preg_match('/^on[a-z0-9_]+$/i', $name) === 1\n        && method_exists($widget, $name);\n}","tryCatchPattern":"try {\n    $result = $widget->runHandler($config, $handler, $extra);\n} catch (SystemException $e) {\n    if (str_contains($e->getMessage(), 'Handler does not exist')) {\n        return response()->json(['error' => 'Unknown handler'], 422);\n    }\n    throw $e;\n}","preventionTips":["Update JS call sites whenever a handler is renamed; version-bust cached assets on deploy.","Keep handler methods public and defined on the widget class that serves the request.","During renames, keep a delegating onOldName() wrapper until front-ends catch up."],"tags":["dashboard","vue-report-widget","ajax-handler","method-not-found"],"backgroundTag":"handler-method-not-found","analyzedSha":"b608633a7e8922487d91a8161499020121c3b3bf","analyzedAt":"2026-08-21T04:24:57.515Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}