phacility/phabricator · error · Exception

You must call %s when rendering an %s.

Error message

You must call %s when rendering an %s.

What it means

Modern Phabricator views require an acting viewer (the logged-in PhabricatorUser) before rendering, because policies, links, and rendering all depend on who is looking. AphrontDialogView::render() enforces this by checking hasViewer() and throwing if render() is reached without a prior setViewer() call. It is a programming error in the calling controller or view, not a runtime data problem.

Source

Thrown at src/view/AphrontDialogView.php:295

      $meta = array();
      if ($this->disableWorkflowOnCancel) {
        $meta['disableWorkflow'] = true;
      }

      $buttons[] = javelin_tag(
        'a',
        array(
          'href'  => $this->cancelURI,
          'class' => 'button button-grey',
          'name'  => '__cancel__',
          'sigil' => 'jx-workflow-button',
          'meta' => $meta,
        ),
        $this->cancelText);
    }

    if (!$this->hasViewer()) {
      throw new Exception(
        pht(
          'You must call %s when rendering an %s.',
          'setViewer()',
          __CLASS__));
    }

    $classes = array();
    $classes[] = 'aphront-dialog-view';
    $classes[] = $this->class;
    if ($this->flush) {
      $classes[] = 'aphront-dialog-flush';
    }

    switch ($this->width) {
      case self::WIDTH_FORM:
      case self::WIDTH_FULL:
        $classes[] = 'aphront-dialog-view-width-'.$this->width;
        break;

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Call '$dialog->setViewer($this->getViewer())' (in a PhabricatorController) or '$dialog->setViewer($request->getViewer())' before producing the response.
  2. If the dialog is built in a helper, pass the viewer in and set it there, so every construction path is covered.
  3. Set the viewer immediately after constructing the view so later refactors cannot drop it.
  4. Search for other view constructions in the same code path — the same omission usually repeats.

Example fix

// before
public function handleRequest(AphrontRequest $request) {
  $dialog = id(new AphrontDialogView())
    ->setTitle(pht('Delete widget'))
    ->addCancelButton('/w/');
  return id(new AphrontResponse())->setContent($dialog->render());
}

// after
public function handleRequest(AphrontRequest $request) {
  $viewer = $this->getViewer();
  $dialog = id(new AphrontDialogView())
    ->setViewer($viewer)
    ->setTitle(pht('Delete widget'))
    ->addCancelButton('/w/');
  return id(new AphrontResponse())->setContent($dialog->render());
}
Defensive patterns

Strategy: validation

Validate before calling

// Always resolve and set the viewer as the first step of dialog construction:
$viewer = $request->getViewer(); // or $this->getViewer() inside a controller
$dialog = id(new AphrontDialogView())
  ->setViewer($viewer)           // set immediately, before any content
  ->setTitle($title);

Prevention

When it happens

Trigger: Building an AphrontDialogView in a controller/handler and returning it (or calling render()) without '$dialog->setViewer($viewer)' first. Typical with ported pre-viewer-era code, dialogs created inside helper methods that never receive the viewer, or dialogs constructed in CLI/AJAX contexts where no viewer was resolved from the request.

Common situations: Upgrading old Phabricator code or extensions written before views required viewers; copy-pasting dialog code from another controller and dropping the setViewer line; rendering a dialog from a workflow handler where $request->getViewer() was never fetched.

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/da294901e297f445. Report an issue: GitHub.