Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Eloquent "Query Builder" with "OR" operator neutralizes my Global Scope - Laravel

β›³ What I need:

I am developing an application in Laravel 5.4 and I want a global scope that allows me to filter different elements of the application depending on the user that created them.


🌍 My global scope:

I have a class BaseEloquentModel.php who extends Eloquent and all my models extends from this class. I have a global scope as follow:

protected static function boot()
{
    parent::boot();
    static::addGlobalScope('', function(\Illuminate\Database\Eloquent\Builder $builder) use($userId) {
        /**
        * I get the name of the table with <code>(with(new static))->getTable()</code> 
        * and then filter the query for the <b>user_id</b> field
        */
        $builder->where(
            (with(new static))->getTable() . '.user_id', 
            '=',
            $userId
        );
    });
}

β›” The problem

When I have a query like this, with or operator, the global scope is "neutralized":

$qBuilderCars = Car::whereRaw("name like ? or id = ?", [
    '%' . $searchCriteria. '%',
    $searchCriteria
]);

If I call the toSql() method on $qBuilderCars I see that it "correctly" adds the AND operator to the end of the query.

select * from `cars` where name like ? or id = ? and user_id = ?

Maybe you've already noticed my problem ... If the element's builder, in this case cars, has used an OR operator, then the global scope will not help, since there is no parenthesis between where name like ? or id = ?. So the resulting query would be something similar to the following:

select * from `cars` where name like ? (or id = ? and user_id = ?)

So this query will return all cars whose name matches or whose ID is the one received and has been created by the user...

When what I need is:

select * from `cars` where (name like ? or id = ?) and user_id = ?

πŸ‘Ž My attempts

I tried to alter my global scope to try to make the AND operator that I add the most restrictive in the query, but without any success.

I can not manually add parentheses to all the application's queries, so ... Is there a way to add global parentheses from the global scope to the builder?


πŸ’‘ The solution

The solution is to add parentheses to all the raw queries.

  • βœ…βœ… You can see the @Styx solution which I consider the most successful

  • I will also leave my answer, which acts directly inside the global scope, and which I consider interesting to be able to see how an \Illuminate\Database\Eloquent\Builder object works

like image 521
tomloprod Avatar asked Apr 19 '19 07:04

tomloprod


1 Answers

Well, it seems that your solution to add parentheses is the best workaround, but I have a suggestion how to do that slightly better way.

  1. Create new class QueryBuilder. For example, in \App\Models\ namespace (app/Models/ folder):

    namespace App\Models;
    
    use Illuminate\Database\Query\Builder as EloquentQueryBuilder;
    
    class QueryBuilder extends EloquentQueryBuilder {
    
      public function whereRaw($sql, $bindings = [], $boolean = 'and')
      {
        return parent::whereRaw('('.$sql.')', $bindings, $boolean);
      }
    
    }
    
  2. Add this code to your BaseEloquentModel class:

    use Illuminate\Database\Eloquent\Model;
    use App\Models\QueryBuilder; // <-- addition
    
    class BaseEloquentModel extends Model {
      // ...
      protected function newBaseQueryBuilder()
      {
        $connection = $this->getConnection();
    
        return new QueryBuilder(
            $connection,
            $connection->getQueryGrammar(),
            $connection->getPostProcessor()
        );
      }
      // ...
    }
    

Now, all whereRaw() calls will automatically have parentheses around query.

like image 114
Styx Avatar answered Nov 16 '22 17:11

Styx