I am running into a REALLY odd issue, and my best guess is that it’s a bug within PHP itself. When comparing (===
) the value of a property on one object with the value of a property on another object, all the values of the properties on one of the objects are deleted.
PHP 8.3.2-1+0~20240120.16+debian11~1.gbpb43448
Laravel Framework 11.4.0
8.0.33-0ubuntu0.20.04.2
From the top
I’m editing a Post
model, and running the update
method on form submit
Routing
Route-model binding works as expected, and the correct controller method is called.
Route::patch('/posts/{id}/update', [PostController::class, 'update'])
->name('post.update');
PostController@update
public function update(UpdatePostRequest $request, Post $post) :RedirectResponse
{
// A quick test here that will become relevant in a moment
// dd(request()->user()->id === $post->user_id); // true
// Results in 403
if (request()->user()->cannot('edit', $post))
abort(403);
.
.
.
}
PostPolicy@edit
The PostPolicy class is called to check if the user can edit the post. The if
statement is false despite the fact that the values being compared are identical.
/**
* Determine if "$user" can perform "edit" on "$post"
*/
public function edit(User $user, Post $post) :bool
{
if ($post->user_id === $user->id) {
// Expecting this to return
return $user->can('edit.own_posts');
}
else{
// Always gets returned
return $user->can('edit.posts');
}
}
Note: I have verified all roles and permissions are properly assigned, although that isn’t really relevant to the issue I’m seeing.
The Problem
In the above function, checking the value of $user
and $post
BEFORE the if
statement yields exactly the values that are expected … $post->user_id
is strictly equal (===
) to $user->id
.
However, checking the value of $post
from within the if
statement block, reveals that all the properties on $post
are empty. They all just disappeared.
Here are the results of various dd()
(dump()
and die()
) calls.
public function edit(User $user, Post $post) :bool
{
dd($user->id); // int 112
dd($post->user_id); // int 112
dd($user->id == $post->user_id); // true
dd($user->id === $post->user_id); // true
// What if accessing the property is what causes it to become null?
// Let's dump it twice.
dd($post->user_id, $post->user_id) // int 112, int 112
// After the comparison, all properties of
// $post are empty
if ($post->user_id === $user->id) {
return $user->can('edit.own_posts');
}
else{
dd($user->id); // int 112
dd($post->user_id); // null
dd($user->id == $post->user_id); // false
dd($user->id === $post->user_id); // false
return $user->can('edit.posts');
}
}
It Gets Weirder
This one is really throwing me off. If, and only if, I place a dd()
inside the if
block, it will execute as if the comparison resulted in true
, but will not execute if I remove the dd()
.
public function edit(User $user, Post $post) :bool
{
if ($post->user_id === $user->id) {
// This line executes when present. Removing it will cause
// the `else` block to execute.
dd($user->id, $post->user_id); // int 112, null
return $user->can('edit.own_posts');
}
else{
// This line only executes if you remove the dd() above
return $user->can('edit.posts');
}
}
No matter what I do, the second return
statement is the only one I can get to. But just for fun, let’s try inverting the logic.
public function edit(User $user, Post $post) :bool
{
if ($post->user_id !== $user->id) {
// Always executes
return $user->can('edit.posts');
}
else{
return $user->can('edit.own_posts');
}
}
For visual reference, here is the dd($post)
result before the comparison call.
And here it is again called from within the if
block.
Has anyone ever seen anything like this before, or have any ideas what could be causing it??