Skip to content

Commit

Permalink
Add (global) macros to Eloquent/Builder. (#17719)
Browse files Browse the repository at this point in the history
  • Loading branch information
reinink authored and taylorotwell committed Feb 7, 2017
1 parent d033626 commit 6ac7fb8
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 26 deletions.
71 changes: 54 additions & 17 deletions src/Illuminate/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,18 @@ class Builder
protected $eagerLoad = [];

/**
* All of the registered builder macros.
* All of the globally registered builder macros.
*
* @var array
*/
protected $macros = [];
protected static $macros = [];

/**
* All of the locally registered builder macros.
*
* @var array
*/
protected $localMacros = [];

/**
* A replacement for the typical delete function.
Expand Down Expand Up @@ -1273,18 +1280,6 @@ public function setModel(Model $model)
return $this;
}

/**
* Extend the builder with a given callback.
*
* @param string $name
* @param \Closure $callback
* @return void
*/
public function macro($name, Closure $callback)
{
$this->macros[$name] = $callback;
}

/**
* Get the given macro by name.
*
Expand All @@ -1293,7 +1288,7 @@ public function macro($name, Closure $callback)
*/
public function getMacro($name)
{
return Arr::get($this->macros, $name);
return Arr::get($this->localMacros, $name);
}

/**
Expand All @@ -1305,10 +1300,24 @@ public function getMacro($name)
*/
public function __call($method, $parameters)
{
if (isset($this->macros[$method])) {
if ($method === 'macro') {
$this->localMacros[$parameters[0]] = $parameters[1];

return;
}

if (isset($this->localMacros[$method])) {
array_unshift($parameters, $this);

return $this->macros[$method](...$parameters);
return $this->localMacros[$method](...$parameters);
}

if (isset(static::$macros[$method]) and static::$macros[$method] instanceof Closure) {
return call_user_func_array(static::$macros[$method]->bindTo($this, static::class), $parameters);
}

if (isset(static::$macros[$method])) {
return call_user_func_array(static::$macros[$method]->bindTo($this, static::class), $parameters);
}

if (method_exists($this->model, $scope = 'scope'.ucfirst($method))) {
Expand All @@ -1324,6 +1333,34 @@ public function __call($method, $parameters)
return $this;
}

/**
* Dynamically handle calls into the query instance.
*
* @param string $method
* @param array $parameters
* @return mixed
*
* @throws \BadMethodCallException
*/
public static function __callStatic($method, $parameters)
{
if ($method === 'macro') {
static::$macros[$parameters[0]] = $parameters[1];

return;
}

if (! isset(static::$macros[$method])) {
throw new BadMethodCallException("Method {$method} does not exist.");
}

if (static::$macros[$method] instanceof Closure) {
return call_user_func_array(Closure::bind(static::$macros[$method], null, static::class), $parameters);
}

return call_user_func_array(static::$macros[$method], $parameters);
}

/**
* Force a clone of the underlying query builder when cloning.
*
Expand Down
11 changes: 10 additions & 1 deletion tests/Database/DatabaseEloquentBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ public function testPluckWithoutModelGetterJustReturnTheAttributesFoundInDatabas
$this->assertEquals(['bar', 'baz'], $builder->pluck('name')->all());
}

public function testMacrosAreCalledOnBuilder()
public function testLocalMacrosAreCalledOnBuilder()
{
unset($_SERVER['__test.builder']);
$builder = new \Illuminate\Database\Eloquent\Builder(new \Illuminate\Database\Query\Builder(
Expand All @@ -371,6 +371,15 @@ public function testMacrosAreCalledOnBuilder()
unset($_SERVER['__test.builder']);
}

public function testGlobalMacrosAreCalledOnBuilder()
{
Builder::macro('foo', function ($bar) {
return $bar;
});

$this->assertEquals($this->getBuilder()->foo('bar'), 'bar');
}

public function testGetModelsProperlyHydratesModels()
{
$builder = m::mock('Illuminate\Database\Eloquent\Builder[get]', [$this->getMockQueryBuilder()]);
Expand Down
28 changes: 20 additions & 8 deletions tests/Database/DatabaseSoftDeletingScopeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ public function testApplyingScopeToABuilder()

public function testRestoreExtension()
{
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
$builder->shouldDeferMissing();
$builder = new \Illuminate\Database\Eloquent\Builder(new \Illuminate\Database\Query\Builder(
m::mock('Illuminate\Database\ConnectionInterface'),
m::mock('Illuminate\Database\Query\Grammars\Grammar'),
m::mock('Illuminate\Database\Query\Processors\Processor')
));
$scope = new \Illuminate\Database\Eloquent\SoftDeletingScope;
$scope->extend($builder);
$callback = $builder->getMacro('restore');
Expand All @@ -41,8 +44,11 @@ public function testRestoreExtension()

public function testWithTrashedExtension()
{
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
$builder->shouldDeferMissing();
$builder = new \Illuminate\Database\Eloquent\Builder(new \Illuminate\Database\Query\Builder(
m::mock('Illuminate\Database\ConnectionInterface'),
m::mock('Illuminate\Database\Query\Grammars\Grammar'),
m::mock('Illuminate\Database\Query\Processors\Processor')
));
$scope = m::mock('Illuminate\Database\Eloquent\SoftDeletingScope[remove]');
$scope->extend($builder);
$callback = $builder->getMacro('withTrashed');
Expand All @@ -56,8 +62,11 @@ public function testWithTrashedExtension()

public function testOnlyTrashedExtension()
{
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
$builder->shouldDeferMissing();
$builder = new \Illuminate\Database\Eloquent\Builder(new \Illuminate\Database\Query\Builder(
m::mock('Illuminate\Database\ConnectionInterface'),
m::mock('Illuminate\Database\Query\Grammars\Grammar'),
m::mock('Illuminate\Database\Query\Processors\Processor')
));
$model = m::mock('Illuminate\Database\Eloquent\Model');
$model->shouldDeferMissing();
$scope = m::mock('Illuminate\Database\Eloquent\SoftDeletingScope[remove]');
Expand All @@ -76,8 +85,11 @@ public function testOnlyTrashedExtension()

public function testWithoutTrashedExtension()
{
$builder = m::mock('Illuminate\Database\Eloquent\Builder');
$builder->shouldDeferMissing();
$builder = new \Illuminate\Database\Eloquent\Builder(new \Illuminate\Database\Query\Builder(
m::mock('Illuminate\Database\ConnectionInterface'),
m::mock('Illuminate\Database\Query\Grammars\Grammar'),
m::mock('Illuminate\Database\Query\Processors\Processor')
));
$model = m::mock('Illuminate\Database\Eloquent\Model');
$model->shouldDeferMissing();
$scope = m::mock('Illuminate\Database\Eloquent\SoftDeletingScope[remove]');
Expand Down

0 comments on commit 6ac7fb8

Please sign in to comment.