UnusedViewData
Emitted when a view() call site passes a data key that the rendered template neither reads nor declares, and neither does any template it hands its whole scope to (@include, @extends). A key nothing reads is work done for nothing, and usually a symptom: a variable renamed in the template and not at the call site, a key that survived a refactor, a compact() list that grew past what the view uses.
Example
// nothing in profile.blade.php reads $subtitle
view('profile', ['name' => $user->name, 'subtitle' => $this->expensiveSubtitle()]); // UnusedViewData
view('profile', ['name' => $user->name]); // OK
A key read or declared by any template reached through an ordinary @include or @extends counts as used, because the include inherits the whole scope. A @var docblock or @props entry states a template’s interface, so declaring a key counts as using it.
How to fix
- Drop the key from the call site.
- If the template should be using it, use it, or declare it (
<?php /** @var T $name */ ?>or@props). This is also the better fix when the key is part of the template’s contract but only reached by code the plugin cannot follow. - Otherwise suppress it at the call site:
@psalm-suppress UnusedViewDataon the statement or the enclosing function.
Limitations
Every gate below silences the check for that call site rather than guessing:
- The template’s read set must be provable. A
$$namewrite,extract(), or acompact()with a non-literal argument anywhere in the compiled output makes the set a lower bound.@propsand@awareboth compile to$$namewrites, so component templates are never checked. - The include chain must be literal. A dynamic
@include($name)at any depth declines for the whole chain. So does a custom directive (for example an app overriding@includewithBlade::directive()) that hands its own renderer the scope the way Blade’s@includedoes, asarray_diff_key(get_defined_vars(), ['__data' => 1, '__path' => 1]), because the plugin cannot know which views it renders. A directive that passes the scope another way (a bareget_defined_vars(), a temporary variable, a spread) is not recognised, so a key only its template reads can still be reported; suppress it there.@includeIsolatedand@eachrender with a fresh scope, so a key only their template reads is still reported, which is correct. @propsmust be readable. A@props($array)the plugin cannot read as a literal list makes the declared set a lower bound, so nothing is reported for that template.- Ambient and framework-supplied names are never reported.
$errors,$slot,$attributes,$component,$loopand$__envare Blade’s own; see also Framework-supplied data. - Loop aliases count as read.
@foreach ($items as $item)marks bothitemsanditemas read: the read set is deliberately template-wide rather than scope-aware, and over-counting a name as used is the safe direction. - A write-only variable does not count as a read.
@php $key = 1; @endphpwithkeypassed in is reported, because the passed value is provably discarded. - Only literal string keys in the data array or a
with()call are checked, on the same supported call sites as the other view data issues. A view with a composer the plugin cannot read is still checked here, so a key only that composer reads can be reported (composers).
For promotion and suppression, see the shared reporting level.