diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index e3fce8a90151..a7306ad15d03 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -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. @@ -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. * @@ -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); } /** @@ -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))) { @@ -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. * diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index a05fcabd4dbe..86d613e8ca3a 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -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( @@ -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()]); diff --git a/tests/Database/DatabaseSoftDeletingScopeTest.php b/tests/Database/DatabaseSoftDeletingScopeTest.php index 3ca8aa2e688d..3520c790fc71 100644 --- a/tests/Database/DatabaseSoftDeletingScopeTest.php +++ b/tests/Database/DatabaseSoftDeletingScopeTest.php @@ -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'); @@ -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'); @@ -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]'); @@ -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]');