Skip to content

Fix parsewidget: Container::set TypeError and deleted items - #463

Merged
daphneslootmans merged 1 commit into
masterfrom
fix/parsewidget
Nov 2, 2026
Merged

daphneslootmans merged 1 commit into
masterfrom
fix/parsewidget

Conversation

@daphneslootmans

@daphneslootmans daphneslootmans commented Nov 2, 2026 •

Copy link
Copy Markdown

Summary

{{ parsewidget() }} is broken: any template that uses it returns a 500.

  • TypeError on every call. parseWidget() calls Container::set('parseWidget', true). Symfony's container only accepts objects there, so it throws Container::set(): Argument #2 ($service) must be of type ?object, true given. Nothing in core reads that flag, so the two set() calls are removed.
  • Deleted items crash the page. When the item behind the id no longer exists (for example a deleted content block, whose modules_extras row is removed as well), getForId() built a widget without data, and the widget then failed on a null template path. That happened outside the try, so it always ended in a 500. getForId() now throws a Frontend\Core\Engine\Exception when a lookup by id finds nothing, and parseWidget() calls it inside the try. Debug mode shows the error, production renders nothing. Lookups without an id (widgets with NULL data) behave as before.

Found on sumocoders-be-2026, which renders a content block on every page with {{ parsewidget('ContentBlocks', 'Detail', 8) }}.

Test plan

  • phpcs (phpcs.xml.dist) and php -l on both files
  • On sumocoders-be-2026 (same code): visible block renders, hidden block renders nothing, deleted block gives 200 without the block with debug off and shows the error with debug on
  • CI: phpstan and PHPUnit
  • Render a template with {{ parsewidget('ContentBlocks', 'Detail', <id>) }} on a fresh install

🤖 Generated with Claude Code

Summary by Sourcery

Prevent parseWidget() from crashing templates when rendering valid or deleted widgets.

Bug Fixes:

  • Fix parseWidget() failures caused by invalid container state handling.
  • Handle missing widgets referenced by ID without causing production pages to return HTTP 500 errors.

Enhancements:

  • Preserve existing behavior for widget lookups without an ID while reporting missing ID-based widgets through the normal frontend error handling.

- parseWidget called Container::set('parseWidget', true). Symfony only
  accepts objects there, so any {{ parsewidget() }} in a template threw
  a TypeError and the page returned a 500. Nothing reads that flag, so
  it is removed.
- getForId built a widget without data when the item behind the id no
  longer exists (e.g. a deleted content block), which crashed outside
  the try. It now throws a Frontend exception, and parseWidget calls it
  inside the try: debug shows the error, production renders nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Fixes parseWidget() failures by removing invalid container state mutations and handling deleted ID-backed widgets inside the caught execution path, allowing production to render no content and debug mode to report the missing widget.

Sequence diagram for parseWidget missing-item handling

sequenceDiagram
    participant Template
    participant TemplateModifiers
    participant Widget
    participant Database
    participant ErrorHandler

    Template->>TemplateModifiers: parseWidget(module, action, id)
    TemplateModifiers->>Widget: getForId(kernel, module, action, id)
    Widget->>Database: getVar(query, parameters)
    Database-->>Widget: null or false
    Widget-->>TemplateModifiers: FrontendException
    TemplateModifiers->>ErrorHandler: catch Exception
    ErrorHandler-->>TemplateModifiers: debug error or empty output
    TemplateModifiers-->>Template: string
Loading

File-Level Changes

Change Details Files
Removed invalid container flag writes from widget parsing and narrowed the execution flow to return rendered widget content directly.
  • Eliminated boolean/null writes to Symfony’s object-only service container.
  • Kept widget execution and content retrieval within the existing exception handling path.
src/Frontend/Core/Engine/TemplateModifiers.php
Added explicit handling for missing widgets when resolving an item by ID.
  • Checks database lookup results for missing ID-backed data.
  • Throws a frontend engine exception for deleted or nonexistent widgets while preserving no-ID lookup behavior.
src/Frontend/Core/Engine/Block/Widget.php

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="src/Frontend/Core/Engine/Block/Widget.php" line_range="255-256" />
<code_context>
-        );
+        // A lookup by id matches on the data column, so no data means the item (and its widget) no longer exists
+        $data = $kernel->getContainer()->get('database')->getVar($query, $parameters);
+        if (is_numeric($id) && ($data === null || $data === false)) {
+            throw new FrontendException('No ' . $module . '::' . $action . ' widget found for id ' . $id . '.');
+        }
+
</code_context>
<issue_to_address>
**Orphaned widgets still crash pages**

When a deleted item still has a matching widget row in `modules_extras`, `getForId()` treats any matching `modules_extras` row as a valid item, so an orphaned row lets the widget execute with a deleted item; the widget can then fail before `parseWidget()`'s `catch (Exception)` handles it, and the page returns a 500.

Validate that the referenced item exists before executing the widget, or handle failures from widget execution that are not `Exception`s.
</issue_to_address>

### Comment 2
<location path="src/Frontend/Core/Engine/TemplateModifiers.php" line_range="454" />
<code_context>
+                $action,
+                $id
+            );
             $extra->execute();
-            $content = $extra->getContent();
-            FrontendModel::getContainer()->set('parseWidget', null);
</code_context>
<issue_to_address>
**Concurrent deletion still returns 500**

When a deletion removes the content block after `getForId()` reads its widget row but before the widget executes, `Detail::execute()` looks up the content block after the widget row was read, and throws `ContentBlockNotFound` if deletion has since removed it. The catch resolves to the frontend engine exception, not this global exception, so it escapes `parseWidget()` and the page returns 500.

Catch global `\Exception` in `parseWidget()` (or explicitly catch `ContentBlockNotFound`) so this deletion race follows the normal production fallback.
</issue_to_address>

Sourcery assessment

Approval pending. 2 findings to address first.

Blocking findings: src/Frontend/Core/Engine/Block/Widget.php:256, src/Frontend/Core/Engine/TemplateModifiers.php:454


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

@daphneslootmans
daphneslootmans merged commit a3672ac into master Nov 2, 2026
9 of 12 checks passed
@daphneslootmans
daphneslootmans deleted the fix/parsewidget branch November 2, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants