Skip to content

Commit 6a3d729

Browse files
committed
feat(stubs): default convention props to fix PropertyNotSetInConstructor
Nova base classes declare display/attribute props via a `@var` docblock with no default (`$name`, `$attribute`, `$resource` on Field; `$name` on Action/Metric/Filter; `$prefix`/`$suffix`/`$format` on TrendResult), so every concrete subclass that omits its own copy is flagged with PropertyNotSetInConstructor. Declare each prop WITH a default on the base-class stub. addStubFile merges (the stub member wins, real members are kept), so the default flips `has_default` and the initialization check is skipped for every subclass that inherits it. Two constraints drive the shape of these stubs: - `$name` on Action/Metric/Filter is declared UNTYPED (type in `@var` only). Subclasses conventionally write `public $name = 'Label'` untyped; a native signature type on the parent makes each of those NonInvariantPropertyType. Field's `$name`/`$attribute` have no such untyped redeclarers, so a native `string` is safe there. - The new Filter/Metric/TrendResult stubs restate the full real hierarchy (extends/implements/use). addStubFile replaces the class signature, so omitting `implements FilterContract` would strip it and make every `Resource::filters()` raise ImplementedReturnTypeMismatch. A Lens stub is intentionally not added: it surfaces a MethodSignatureMismatch on subclasses that widen the `query()` builder param (PHP-legal contravariance that Psalm only enforces against stub parents, not vendor parents), which is not worth the few fixes it would provide. Net on the consuming project: -37 PropertyNotSetInConstructor, no new issues.
1 parent c15b713 commit 6a3d729

6 files changed

Lines changed: 84 additions & 0 deletions

File tree

src/Plugin.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ public function __invoke(RegistrationInterface $registration, ?\SimpleXMLElement
3232
$stubsDir = __DIR__.'/../stubs/Nova';
3333
$registration->addStubFile($stubsDir.'/Actions/Action.phpstub');
3434
$registration->addStubFile($stubsDir.'/Fields/Field.phpstub');
35+
$registration->addStubFile($stubsDir.'/Filters/Filter.phpstub');
3536
$registration->addStubFile($stubsDir.'/Element.phpstub');
37+
$registration->addStubFile($stubsDir.'/Metrics/Metric.phpstub');
3638
$registration->addStubFile($stubsDir.'/Metrics/PartitionResult.phpstub');
39+
$registration->addStubFile($stubsDir.'/Metrics/TrendResult.phpstub');
3740
$registration->addStubFile($stubsDir.'/Panel.phpstub');
3841
$registration->addStubFile($stubsDir.'/Resource.phpstub');
3942
}

stubs/Nova/Actions/Action.phpstub

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,15 @@ class Action implements \JsonSerializable
1515
use \Illuminate\Support\Traits\Tappable;
1616
use \Laravel\Nova\WithComponent;
1717

18+
/**
19+
* Untyped (no native signature type) so subclasses declaring the display name as
20+
* `public $name = '…'` (Nova's convention, untyped) stay invariant. The default value
21+
* is what satisfies PropertyNotSetInConstructor on subclasses that omit their own `$name`.
22+
*
23+
* @var \Stringable|string
24+
*/
25+
public $name = '';
26+
1827
/**
1928
* Get the fields displayed by the resource.
2029
*

stubs/Nova/Fields/Field.phpstub

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,9 @@ abstract class Field extends \Laravel\Nova\Fields\FieldElement implements \JsonS
2323
use \Laravel\Nova\Fields\PreviewableFields;
2424
use \Laravel\Nova\Fields\SupportsFullWidthFields;
2525
use \Illuminate\Support\Traits\Tappable;
26+
27+
public string $name = '';
28+
public string $attribute = '';
29+
public object | array $resource = [];
30+
protected ?bool $dependentShouldEmitChangesEvent = null;
2631
}

stubs/Nova/Filters/Filter.phpstub

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace Laravel\Nova\Filters;
4+
5+
/**
6+
* Full hierarchy restated (interfaces + traits): addStubFile replaces the class signature,
7+
* so omitting `implements FilterContract` would strip it and every Resource::filters()
8+
* returning concrete filters would raise ImplementedReturnTypeMismatch.
9+
*/
10+
abstract class Filter implements \Laravel\Nova\Contracts\Filter, \JsonSerializable
11+
{
12+
use \Laravel\Nova\AuthorizedToSee;
13+
use \Illuminate\Support\Traits\Macroable;
14+
use \Laravel\Nova\Makeable;
15+
use \Laravel\Nova\Metable;
16+
use \Laravel\Nova\ProxiesCanSeeToGate;
17+
use \Laravel\Nova\Filters\Searchable;
18+
use \Laravel\Nova\WithComponent;
19+
20+
/**
21+
* Untyped (no native signature type) so subclasses declaring the display name as
22+
* `public $name = '…'` (Nova's convention, untyped) stay invariant. The default value
23+
* is what satisfies PropertyNotSetInConstructor on subclasses that omit their own `$name`.
24+
*
25+
* @var string
26+
*/
27+
public $name = '';
28+
}

stubs/Nova/Metrics/Metric.phpstub

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Laravel\Nova\Metrics;
4+
5+
/**
6+
* Full hierarchy restated (parent + traits): addStubFile replaces the class signature,
7+
* so omitting `extends Card` would strip Card from every metric's ancestry.
8+
*/
9+
abstract class Metric extends \Laravel\Nova\Card
10+
{
11+
use \Laravel\Nova\Metrics\HasHelpText;
12+
use \Laravel\Nova\ResolvesFilters;
13+
14+
/**
15+
* Untyped (no native signature type) so subclasses declaring the display name as
16+
* `public $name = '…'` (Nova's convention, untyped) stay invariant. The default value
17+
* is what satisfies PropertyNotSetInConstructor on subclasses that omit their own `$name`.
18+
*
19+
* @var \Stringable|string
20+
*/
21+
public $name = '';
22+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace Laravel\Nova\Metrics;
4+
5+
/**
6+
* Full hierarchy restated (interface + trait): addStubFile replaces the class signature.
7+
* The `@var string` convention properties are set by Nova internals, not a constructor,
8+
* so each needs a default to satisfy PropertyNotSetInConstructor on subclasses.
9+
*/
10+
class TrendResult implements \JsonSerializable
11+
{
12+
use \Laravel\Nova\Metrics\TransformsResults;
13+
14+
public string $prefix = '';
15+
public string $suffix = '';
16+
public string $format = '';
17+
}

0 commit comments

Comments
 (0)