Skip to content

[13.x] Add fluent dump methods to Eloquent models#60296

Closed
andreapozza wants to merge 2 commits into
laravel:13.xfrom
andreapozza:13.x_add_fluent_dump_methods_to_eloquent_models
Closed

[13.x] Add fluent dump methods to Eloquent models#60296
andreapozza wants to merge 2 commits into
laravel:13.xfrom
andreapozza:13.x_add_fluent_dump_methods_to_eloquent_models

Conversation

@andreapozza
Copy link
Copy Markdown

Summary

This PR introduces fluent dump() and dd() debugging methods directly to Eloquent models by leveraging the Illuminate\Support\Traits\Dumpable trait.

This change leaves the static behavior entirely unchanged: calling User::dump() or User::dd() will continue to be forwarded to the Query Builder as before.

Why this PR? (Fixing Unexpected Behavior)

The main driver behind this PR is addressing an unexpected behavior in the developer experience when debugging hydrated models.

Currently, the Eloquent Query Builder is fully inspectable mid-chain. However, if a developer attempts to fluently call ->dump() or ->dd() on a hydrated model instance, the call is caught by the magic __call() method and forwarded down to a new Query Builder instance. Instead of inspecting the model's current state, the developer unexpectedly gets a dump of the Query Builder itself.

By adding the Dumpable trait directly to the Model class, we resolve this friction:

  • Fixes Developer Intuition: Calling ->dump() on an instantiated model now actually dumps that specific model instance (its attributes, relations, and mutated state) rather than routing back to the builder.
  • Preserves Fluent Chains: Developers can safely inspect an instance mid-operation or within collection pipelines without breaking the execution flow or relying on temporary variables.

The Alias Trick (dump as protected)

To achieve this without breaking backwards compatibility or creating conflicts with the magic __call routing, this PR utilizes an aliasing trick when importing the trait:

use Dumpable {
    dump as protected;
    dd as protected;
}

By aliasing the trait's methods as protected, we prevent them from hiding or overriding the public static/magic behavior blindly. Instead, it allows us to intercept the calls explicitly inside __call() and __callStatic(). This ensures that:

  1. Dynamic calls on a hydrated instance hit __call(), match the array check, and safely execute the protected trait method internally on $this.
  2. Static calls: When called statically, the call hits __callStatic(). It is explicitly intercepted by the in_array check and safely forwarded to a new Query Builder instance (static::query()->$method(...)). This ensures that the static behavior remains completely untouched and backward-compatible.

Usage Example

// THE PROBLEM (Current unexpected behavior)
$user = User::find(1); 
$user->dd(); // Unexpectedly dumps a Query Builder instance, NOT the $user model!

// THE SOLUTION (With this PR)
User::find(1)
    ->dd()   // Correctly dumps the hydrated User instance
    ->update(['status' => 'active']);

// UNCHANGED (Static calls still forward to the Query Builder safely thanks to the alias trick)
User::dd();

@taylorotwell
Copy link
Copy Markdown
Member

Thanks for your pull request to Laravel!

Unfortunately, I'm going to delay merging this code for now. To preserve our ability to adequately maintain the framework, we need to be very careful regarding the amount of code we include.

If applicable, please consider releasing your code as a package so that the community can still take advantage of your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants