livewire/livewire: Form objects now share property handling with components via InteractsWithProperties (#10315)
Contributed by ghabriel25
Part of the livewire v4.4.7 release
Pull request #10315 is a small but satisfying piece of internal cleanup on the 4.x line: the Livewire\Form base class that powers form objects no longer carries its own hand-rolled copies of seven property-handling methods. It now uses the exact same Livewire\Concerns\InteractsWithProperties trait that Livewire components have always relied on. If you're upgrading from v3.8.10, the practical impact is close to zero — the public form object API is unchanged and every test passes — but there is one footgun to avoid if you use traits inside your own form subclasses, plus a minor visibility change to resetExcept() worth knowing about.
What changed
Two files were touched. In src/Features/SupportFormObjects/Form.php, the Form class now composes InteractsWithProperties alongside its existing concerns, and the duplicated implementations of hasProperty(), getPropertyValue(), only(), except(), fill(), pull() and resetExcept() were deleted. The now-unused Illuminate\Database\Eloquent\Model import went with them, since the trait's identical fill() also converts Eloquent models to arrays.
In src/Concerns/InteractsWithProperties.php, resetExcept() was widened from protected to public. That bump exists because trait methods adopt the trait's visibility, and form objects have always exposed these helpers publicly — so keeping Form's existing API intact required it.
One method was deliberately not delegated to the trait: reset().
Why it matters
Seven copies of the same code are seven chances to drift. Until now, a bug fix or improvement to property handling in InteractsWithProperties would not automatically reach form objects, and vice versa. By composing the trait, Form inherits one canonical implementation, and any future change to property resolution lands in both components and form objects at once.
The duplication likely existed out of caution — a worry that reusing the same trait on Form might collide with Component's use of it. In practice there is no conflict: Form doesn't extend Component (it's a plain Arrayable class that receives the component instance through its constructor), and the full test suite passes untouched.
What stays the same for your code
Everyday form object code needs no changes. All the helpers behave exactly as they did on v3.8.10, because the deleted methods were verbatim copies of the trait methods that now serve in their place:
use Livewire\Form;
class PostForm extends Form
{
public $title = '';
public $body = '';
public function submit()
{
$this->validate();
// Identical behavior to v3.8.10:
$this->only('title'); // ['title' => ...]
$this->except('title'); // ['body' => ...]
$this->pull('title'); // returns the value, then resets it
$this->fill($somePost); // still accepts Eloquent models
$this->resetExcept('title'); // resets everything but title
$this->reset();
}
}
Nothing to migrate here — just be aware the implementations now live in Livewire\Concerns\InteractsWithProperties if you ever step through them in a debugger.
One edge case: don't re-use the trait in a form subclass
This is the one caution from the PR. In PHP, a trait's methods take precedence over methods inherited from a parent class. So if you pull the trait into your own Form subclass, the trait's methods will "override it all" — including the form-aware reset() that Livewire\Form provides:
use Livewire\Concerns\InteractsWithProperties;
use Livewire\Form;
class PostForm extends Form
{
use InteractsWithProperties; // ❌ don't do this
public $title;
public $body;
}
After this, $this->reset() inside your form object resolves to the component-oriented trait version instead of the implementation that restores properties from the parent component's captured initial state — so resets would silently stop behaving correctly. The fix is simple: don't opt in. Form already applies the trait for you. If you need to customize a method in a subclass, override it directly in the class body (class methods beat trait methods) rather than mixing in the whole trait.
Notes on reset() and the public resetExcept()
Two closing details. First, Form::reset() intentionally keeps its own implementation: it resets properties back to the values captured by the parent component, a different flow from the trait's version. Wiring it through the trait would make the two call into each other recursively, so the PR author left it alone — it remains the single, deliberate override of trait behavior in Form.
Second, resetExcept() is now public everywhere, since components also use this trait. That's a pure visibility widening: calls inside your classes keep working as before, nothing breaks, and static analyzers or IDE stubs that still describe it as protected can be updated. It also means you can now reach $form->resetExcept(...) or $component->resetExcept(...) from anywhere the object is reachable, such as a test.
Takeaways
Livewire\Formnow usesInteractsWithProperties; seven duplicated methods were removed with no behavior change.- The form object API (
only,except,fill,pull,hasProperty,getPropertyValue,reset,resetExcept) is identical to v3.8.10 — no migration steps needed. InteractsWithProperties::resetExcept()went fromprotectedtopublic, a safe widening.- Never
use InteractsWithPropertiesinside aFormsubclass — override methods in the class body instead. Form::reset()keeps its own form-aware implementation on purpose; it is not the trait's version.