Skip to content

fix(filament): guard ProductForm status callouts against a null record - #2584

Open
ShamarKellman wants to merge 1 commit into
lunarphp:2.xfrom
ShamarKellman:fix/product-form-null-record-on-create
Open

fix(filament): guard ProductForm status callouts against a null record#2584
ShamarKellman wants to merge 1 commit into
lunarphp:2.xfrom
ShamarKellman:fix/product-form-null-record-on-create

Conversation

@ShamarKellman

Copy link
Copy Markdown

Problem

Mounting Schemas\Product\ProductForm without a record — as any resource Create page does — throws a TypeError on every load, before the form can render.

The status-callout closures in getStatusShouts() type-hint a non-nullable Model $record, and getVariantAttributeDataComponent() a non-nullable Product $record. Filament injects a null record on create, so evaluation fails at the closure boundary — the existing null-safe isDraft() / isPublished() helpers never get a chance to run.

TypeError: {closure}(): Argument #1 ($record) must be of type Illuminate\Database\Eloquent\Model, null given
  at packages/filament/src/Schemas/Product/ProductForm.php:60

Reproduced on 2.x. A downstream panel that wires ProductForm::configure() onto a Create page hits this immediately (the bundled lunarphp/admin ProductResource ships no standard Create page, which is why it hasn't surfaced there).

Fix

  • Widen the six status-callout closures to ?Model $record and the variant closure to ?Product $record.
  • Only the two closures that dereference the record directly — $record->channels() and $record->hasVariants — gain an explicit $record === null || guard. The rest already short-circuit through the nullable isPublished()/isDraft() helpers, so no redundant null check is added.
  • hasEnabledCustomerGroup() and isDefaultGroupVisibleToGuests() now accept ?Model and guard internally, matching the existing isPublished()/isDraft()/isArchived() helpers.

Behaviour is unchanged once a record exists. No public method signatures change (widening a parameter type is contravariant / LSP-safe); README is untouched, CHANGELOG gets a bugfix entry.

Tests

New bridge-suite test tests/filament/Unit/Schemas/Product/ProductFormTest.php:

  • Evaluates every status component inside a real schema container with a null record — fails with the TypeError before this change, passes after.
  • Guards that the draft callout still tracks a persisted product's status (visible for a draft, hidden for a published product).
vendor/bin/pest --testsuite=filament   # 47 passed
vendor/bin/phpstan analyse -c phpstan.neon.dist   # no errors (level 0)
vendor/bin/pint --dirty                # pass

Mounting ProductForm without a record — as a resource Create page does —
threw a TypeError on every load. The status-callout closures type-hinted a
non-nullable `Model $record`, and the variant-attributes field a non-nullable
`Product $record`, so Filament injecting a `null` record failed at the closure
boundary before any of the existing null-safe `isDraft()`/`isPublished()`
guards could run.

Widen the closure parameters to `?Model` / `?Product`. The status callouts
already short-circuit through the nullable `isPublished()`/`isDraft()` helpers,
so only the two closures that dereference the record directly (`channels()` and
`hasVariants`) gain an explicit null check. `hasEnabledCustomerGroup()` and
`isDefaultGroupVisibleToGuests()` now accept a nullable record and guard
internally, matching the existing `isPublished()`/`isDraft()`/`isArchived()`
helpers. Behaviour is unchanged once a record exists.

Adds a bridge-suite regression test that evaluates every status component
inside a real schema container with a null record, plus a guard that the draft
callout still tracks a persisted product's status.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

1 participant