{"record":{"id":"8f7fe0a701c163bf","repo":"yiisoft/yii2","slug":"unexpected-calledclass-end-call-a-matching-b","errorCode":null,"errorMessage":"Unexpected {calledClass}::end() call. A matching begin() is not found.","messagePattern":"Unexpected (.+?)::end\\(\\) call\\. A matching begin\\(\\) is not found\\.","errorType":"exception","errorClass":"InvalidCallException","httpStatus":null,"severity":"error","filePath":"framework/base/Widget.php","lineNumber":128,"sourceCode":"            $widget = array_pop(self::$stack);\n\n            $calledClass = self::$_resolvedClasses[get_called_class()] ?? get_called_class();\n\n            if (get_class($widget) === $calledClass) {\n                /** @var static $widget */\n                if ($widget->beforeRun()) {\n                    $result = $widget->run();\n                    $result = $widget->afterRun($result);\n                    echo $result;\n                }\n\n                return $widget;\n            }\n\n            throw new InvalidCallException('Expecting end() of ' . get_class($widget) . ', found ' . get_called_class());\n        }\n\n        throw new InvalidCallException('Unexpected ' . get_called_class() . '::end() call. A matching begin() is not found.');\n    }\n\n    /**\n     * Creates a widget instance and runs it.\n     * The widget rendering result is returned by this method.\n     * @param array $config name-value pairs that will be used to initialize the object properties\n     * @return string the rendering result of the widget.\n     * @throws \\Throwable\n     */\n    public static function widget($config = [])\n    {\n        ob_start();\n        ob_implicit_flush(false);\n        try {\n            $config['class'] = get_called_class();\n            /** @var self $widget */\n            $widget = Yii::createObject($config);\n            $out = '';","sourceCodeStart":110,"sourceCodeEnd":146,"githubUrl":"https://github.com/yiisoft/yii2/blob/66f00d18a29b520f85e8e8f1e32d1e7e7b556cac/framework/base/Widget.php#L110-L146","documentation":"Thrown by yii\\base\\Widget::end() when the static widget stack Widget::$stack is empty, i.e. end() was called with no preceding begin() still pending. begin() pushes onto the shared stack and end() pops; an empty stack means every begin() was already closed or never ran, so there is nothing to end.","triggerScenarios":"Calling Widget::end() standalone in a view; a conditional begin() with an unconditional end(): if ($cond) { Xxx::begin(); } ... Xxx::end(); an earlier exception mid-render unwinding/popping the stack so a later end() finds it empty (e.g. Yii's error handling or Gii/DeepClone style stack resets between tests); begin() in one partial and end() in another that is rendered independently.","commonSituations":"Editing template conditionals around widget wrappers and forgetting to move the end(); splitting a widget wrapper across layout/partial boundaries during refactoring; test suites rendering partial views out of order after an earlier failure left the stack drained.","solutions":["Ensure every end() has a matching begin() executed on every code path — move both inside the same conditional block or wrap the whole region.","Audit partials/layouts for orphan end() calls left behind after a refactor (search for '::end()' without a preceding '::begin()' in the same file chain).","For stateless widgets, use Widget::widget([...]) which needs no pairing.","In long-running workers/tests, treat leftover or drained Widget::$stack as a signal to reset state between render runs (Widget::$stack = [])."],"exampleFix":"// before\n<?php if ($showSearch): ?>\n    <?= SearchBox::begin() ?>\n<?php endif; ?>\n... content ...\n<?= SearchBox::end() ?> // begin() skipped when $showSearch is false -> throws\n\n// after\n<?php if ($showSearch): ?>\n    <?= SearchBox::begin() ?>\n    ... content ...\n    <?= SearchBox::end() ?>\n<?php else: ?>\n    ... content ...\n<?php endif; ?>","handlingStrategy":"validation","validationCode":"// Guard before an end() that may be orphaned by control flow\nif (empty(\\yii\\base\\Widget::$stack)) {\n    \\Yii::warning('Skipped orphan Widget::end() call.', 'widgets');\n    return; // or restructure so begin()/end() share the same branch\n}\nSearchBox::end();","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Put begin() and end() inside the same conditional block — never split the pair across branches","Never split a widget pair across separately rendered partials","After test failures mid-render, reset Widget::$stack = [] between test cases to avoid stale/empty-stack surprises"],"tags":["php","yii2","widget","views","rendering","control-flow"],"backgroundTag":"unbalanced-begin-end-calls","analyzedSha":"66f00d18a29b520f85e8e8f1e32d1e7e7b556cac","analyzedAt":"2026-08-17T05:17:23.470Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}