Snapey's avatar
Level 122

What do you expect to happen with this Eloquent query?

See if you fall into the same trap as me?

        Connection::query()
            ->where(function(Builder $q) use($link_from_type, $link_from_id, $link_to_type, $link_to_id){
                $q->where([
                        'link_from_type' => $link_from_type,
                        'link_from_id' => $link_from_id,
                        'link_to_type' => $link_to_type,
                        'link_to_id' => $link_to_id,
                  ])
                  ->orWhere([
                        'link_from_type' => $link_to_type,
                        'link_from_id' => $link_to_id,
                        'link_to_type' => $link_from_type,
                        'link_to_id' => $link_from_id,
                  ]);
            })
            ->delete();

Connection is between two other models, but they could be presented in either order.

0 likes
7 replies
iamgeorge's avatar

Hi, This Eloquent query is designed to delete records from the connections table based on specific conditions.

The query is initialized on the connections table using Connection::query(). This starts a new query builder instance for the connections model.

Conditional Where Clauses: The where method is used to group two sets of conditions. This is done within a nested where clause using a closure (anonymous function).

First Set of Conditions: Inside the closure, the query checks for records where:

link_from_type equals $link_from_type link_from_id equals $link_from_id link_to_type equals $link_to_type link_to_id equals $link_to_id Or Conditions: The orWhere method is then used to specify an alternative set of conditions. This means that if the records do not match the first set of conditions, they will be checked against the following:

link_from_type equals $link_to_type link_from_id equals $link_to_id link_to_type equals $link_from_type link_to_id equals $link_from_id Delete Operation: After applying the conditions, the delete method is called to remove all records that match either of the two sets of conditions.

This query will delete records from the connections table where either: The combination of link_from_type, link_from_id, link_to_type, and link_to_id matches the specified values, or The combination of link_from_type, link_from_id, link_to_type, and link_to_id matches the values in reverse. In other words, it looks for records where the specified link configuration or its reversed counterpart matches and deletes those records.

2 likes
JussiMannisto's avatar

That's nasty.

I knew it had to do with orWhere not using a closure. So I thought maybe missing parentheses.

That's not a behavior I would've guessed.

1 like
MohamedTammam's avatar

At first look I see no issues. However, after your comment that there's a trap. I investigated the issue 😀

The problem is with orWhere.

First where is doing what is expected which is

(
	link_from_type' = $link_from_type
	AND
	link_from_id =  $link_from_id
	AND
	link_to_type = $link_to_type
	AND
	link_to_id = $link_to_id
)

However the orWhere is using OR in the combined condition instead of AND

(
	link_from_type' = $link_from_type
	OR
	link_from_id =  $link_from_id
	OR
	link_to_type = $link_to_type
	OR
	link_to_id = $link_to_id
)

Usually in conditions like this, I always use callback to combine conditions instead of passing them as an array.

2 likes
amitsolanki24_'s avatar
Connection::query()
            ->where(function(Builder $q) use($link_from_type, $link_from_id, $link_to_type, $link_to_id){
       $q->where([
             'link_from_type' => $link_from_type,
             'link_from_id' => $link_from_id,
             'link_to_type' => $link_to_type,
             'link_to_id' => $link_to_id,
      ]);
})
->orWhere(function(Builder $q) 
use($link_from_type, $link_from_id, $link_to_type, $link_to_id){
     $q->where([
         'link_from_type' => $link_to_type,
         'link_from_id' => $link_to_id,
         'link_to_type' => $link_from_type,
         'link_to_id' => $link_from_id,
      ]);
  })
->delete();

I think this will delete exactly record.

1 like
Snapey's avatar
Level 122

Yes, there were two problems.

The first was confusion about where to add the brackets knowing that I knew that eloquent would be applying ->where('deleted_at', null). But of course I needn't have worried since eloquent adds additional brackets as required.

The second issue was that I hadn't appreciated that orWhere with an array of conditions would apply or to every condition in the array.

What surprised me was that the messed up conditions selected every record instead of none. It was only a test database of 20k records but every one of them was marked as soft deleted. The query was easily fixed (before I even posted) but thought it was a learning oportunity.

Thanks @jussimannisto @mohamedtammam @amitsolanki24_ for your responses

2 likes
jlrdw's avatar

@Snapey After going over it, I had to take two aspirins. 😁

Please or to participate in this conversation.