Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to customize Laravel's Database\Query\Builder (make better subquery)

I'm working on Laravel 4. As I knew, I can do subquery:

Project::whereIn('project_id', function($q) {
    $q->select('project_id')
        ->from('company')
        ->whereNull('deleted_at');
});

I found complications, that I can't use scope in subquery and disable soft_delete make me change source code so much.

I wish it was:

Project::whereIn('project_id', function(&$q) {
    $q = Company::select('project_id')->getQuery();
});

Now, I can add scope, disable soft_delete easily.

I tried, and found a solution, that I must change Laravel's Database\Query\Builder code, function whereInSub, line 786.

call_user_func($callback, $query = $this->newQuery());

to:

$query = $this->newQuery();
call_user_func_array($callback, array(&$query));

It's harmful to modify Laravel framework's vendor. So I want to ask how to do it safely.

Sorry because my bad English.

Thank you for reading.

like image 484
Trung Avatar asked Jul 03 '14 13:07

Trung


2 Answers

Oooh! This is quite a tricky one since your model would extend Eloquent, then Eloquent uses Illuminate\Database\Query\Builder.

But what I noticed is that Eloquent is actually an alias in app/config/app.php file. So what you can do is following these steps.

  1. Extend Illuminate\Database\Query\Builder to MyQueryBuilder with your custom whereInSub().
  2. Extend Illuminate\Database\Eloquent\Model to MyModel and make it use your MyQueryBuilder.
  3. Set Eloquent alias in app/config/app.php to your new MyModel class.

Something like this:

MyQueryBuilder.php:

use Closure;
use Illuminate\Support\Collection;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\Query\Grammars\Grammar;
use Illuminate\Database\Query\Processors\Processor;

class MyQueryBuilder extends Illuminate\Database\Query\Builder
{
    protected function whereInSub($column, Closure $callback, $boolean, $not)
    {
        $type = $not ? 'NotInSub' : 'InSub';

        $query = $this->newQuery(); // Your changes
        call_user_func_array($callback, array(&$query)); // Your changes

        $this->wheres[] = compact('type', 'column', 'query', 'boolean');

        $this->mergeBindings($query);

        return $this;
    }
}

MyModel.php:

use DateTime;
use ArrayAccess;
use Carbon\Carbon;
use LogicException;
use Illuminate\Events\Dispatcher;
use Illuminate\Database\Eloquent\Relations\Pivot;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Contracts\JsonableInterface;
use Illuminate\Support\Contracts\ArrayableInterface;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
// use Illuminate\Database\Query\Builder as QueryBuilder;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\ConnectionResolverInterface as Resolver;
use MyQueryBuilder as QueryBuilder; // MyModel should now use your MyQueryBuilder instead of the default which I commented out above

abstract class MyModel extends Illuminate\Database\Eloquent\Model
{

}

app/config/app.php:

'aliases' => array(
    ...
    'Eloquent'        => 'MyModel',
    ...
);

Note that I put long lists of use up there because "use" keyword does not get inherited. Also I did not put MyQueryBuilder and MyModel in a namespace for the sake of simplicity. My use list might also be different from yours depending on Laravel versions we use, so please check the uses too.

like image 182
Unnawut Avatar answered Sep 19 '22 21:09

Unnawut


You can create a custom query builder and use it like this.

My Custom Query Builder:

use Illuminate\Database\Query\Builder as QueryBuilder;

class MyCustomBuilder extends QueryBuilder{

protected function whereInSub($column, Closure $callback, $boolean, $not)
{
       $type = $not ? 'NotInSub' : 'InSub';

       $query = $this->newQuery(); // Your changes
       call_user_func_array($callback, array(&$query)); // Your changes

       $this->wheres[] = compact('type', 'column', 'query', 'boolean');

       $this->mergeBindings($query);

       return $this;
    }
}

In any Model overwrite the method newBaseQueryBuilder() and return an instance of your own query builder

class MyModel extends Model{
   protected function newBaseQueryBuilder()
   {
      $connection = $this->getConnection();

      return new MyQueryBuilder(
           $connection, $connection->getQueryGrammar(), 
              $connection->getPostProcessor()
    );
}
}
like image 28
Jetmir Haxhisefa Avatar answered Sep 19 '22 21:09

Jetmir Haxhisefa