diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 9f4e83a2887c..9df7d46eec43 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -182,6 +182,13 @@ class Builder */ public $lock; + /** + * The callbacks that should be invoked before the query is executed. + * + * @var array + */ + public $beforeQueryCallbacks = []; + /** * All of the available clause operators. * @@ -2256,6 +2263,33 @@ public function sharedLock() return $this->lock(false); } + /** + * Register a closure to be invoked before the query is executed. + * + * @param callable $callback + * @return $this + */ + public function beforeQuery(callable $callback) + { + $this->beforeQueryCallbacks[] = $callback; + + return $this; + } + + /** + * Invoke the "before query" modification callbacks. + * + * @return void + */ + public function applyBeforeQueryCallbacks() + { + foreach ($this->beforeQueryCallbacks as $callback) { + $callback($this); + } + + $this->beforeQueryCallbacks = []; + } + /** * Get the SQL representation of the query. * @@ -2263,6 +2297,8 @@ public function sharedLock() */ public function toSql() { + $this->applyBeforeQueryCallbacks(); + return $this->grammar->compileSelect($this); } @@ -2663,6 +2699,8 @@ public function implode($column, $glue = '') */ public function exists() { + $this->applyBeforeQueryCallbacks(); + $results = $this->connection->select( $this->grammar->compileExists($this), $this->getBindings(), ! $this->useWritePdo ); @@ -2901,6 +2939,8 @@ public function insert(array $values) } } + $this->applyBeforeQueryCallbacks(); + // Finally, we will run this query against the database connection and return // the results. We will need to also flatten these bindings before running // the query so they are all in one huge, flattened array for execution. @@ -2931,6 +2971,8 @@ public function insertOrIgnore(array $values) } } + $this->applyBeforeQueryCallbacks(); + return $this->connection->affectingStatement( $this->grammar->compileInsertOrIgnore($this, $values), $this->cleanBindings(Arr::flatten($values, 1)) @@ -2946,6 +2988,8 @@ public function insertOrIgnore(array $values) */ public function insertGetId(array $values, $sequence = null) { + $this->applyBeforeQueryCallbacks(); + $sql = $this->grammar->compileInsertGetId($this, $values, $sequence); $values = $this->cleanBindings($values); @@ -2962,6 +3006,8 @@ public function insertGetId(array $values, $sequence = null) */ public function insertUsing(array $columns, $query) { + $this->applyBeforeQueryCallbacks(); + [$sql, $bindings] = $this->createSub($query); return $this->connection->affectingStatement( @@ -2978,6 +3024,8 @@ public function insertUsing(array $columns, $query) */ public function update(array $values) { + $this->applyBeforeQueryCallbacks(); + $sql = $this->grammar->compileUpdate($this, $values); return $this->connection->update($sql, $this->cleanBindings( @@ -3035,6 +3083,8 @@ public function upsert(array $values, $uniqueBy, $update = null) $update = array_keys(reset($values)); } + $this->applyBeforeQueryCallbacks(); + $bindings = $this->cleanBindings(array_merge( Arr::flatten($values, 1), collect($update)->reject(function ($value, $key) { @@ -3109,6 +3159,8 @@ public function delete($id = null) $this->where($this->from.'.id', '=', $id); } + $this->applyBeforeQueryCallbacks(); + return $this->connection->delete( $this->grammar->compileDelete($this), $this->cleanBindings( $this->grammar->prepareBindingsForDelete($this->bindings) @@ -3123,6 +3175,8 @@ public function delete($id = null) */ public function truncate() { + $this->applyBeforeQueryCallbacks(); + foreach ($this->grammar->compileTruncate($this) as $sql => $bindings) { $this->connection->statement($sql, $bindings); } diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 5b882f9ef58a..5adc3a6d9bb4 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\Database; use BadMethodCallException; +use Closure; use DateTime; use Illuminate\Database\ConnectionInterface; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; @@ -2563,6 +2564,116 @@ public function testTruncateMethod() ], $sqlite->compileTruncate($builder)); } + public function testPreserveAddsClosureToArray() + { + $builder = $this->getBuilder(); + $builder->beforeQuery(function () { + }); + $this->assertCount(1, $builder->beforeQueryCallbacks); + $this->assertInstanceOf(Closure::class, $builder->beforeQueryCallbacks[0]); + } + + public function testApplyPreserveCleansArray() + { + $builder = $this->getBuilder(); + $builder->beforeQuery(function () { + }); + $this->assertCount(1, $builder->beforeQueryCallbacks); + $builder->applyBeforeQueryCallbacks(); + $this->assertCount(0, $builder->beforeQueryCallbacks); + } + + public function testPreservedAreAppliedByToSql() + { + $builder = $this->getBuilder(); + $builder->beforeQuery(function ($builder) { + $builder->where('foo', 'bar'); + }); + $this->assertSame('select * where "foo" = ?', $builder->toSql()); + $this->assertEquals(['bar'], $builder->getBindings()); + } + + public function testPreservedAreAppliedByInsert() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('insert')->once()->with('insert into "users" ("email") values (?)', ['foo']); + $builder->beforeQuery(function ($builder) { + $builder->from('users'); + }); + $builder->insert(['email' => 'foo']); + } + + public function testPreservedAreAppliedByInsertGetId() + { + $this->called = false; + $builder = $this->getBuilder(); + $builder->getProcessor()->shouldReceive('processInsertGetId')->once()->with($builder, 'insert into "users" ("email") values (?)', ['foo'], 'id'); + $builder->beforeQuery(function ($builder) { + $builder->from('users'); + }); + $builder->insertGetId(['email' => 'foo'], 'id'); + } + + public function testPreservedAreAppliedByInsertUsing() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert into "users" () select *', []); + $builder->beforeQuery(function ($builder) { + $builder->from('users'); + }); + $builder->insertUsing([], $this->getBuilder()); + } + + public function testPreservedAreAppliedByUpsert() + { + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert into `users` (`email`) values (?) on duplicate key update `email` = values(`email`)', ['foo']); + $builder->beforeQuery(function ($builder) { + $builder->from('users'); + }); + $builder->upsert(['email' => 'foo'], 'id'); + } + + public function testPreservedAreAppliedByUpdate() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('update')->once()->with('update "users" set "email" = ? where "id" = ?', ['foo', 1]); + $builder->from('users')->beforeQuery(function ($builder) { + $builder->where('id', 1); + }); + $builder->update(['email' => 'foo']); + } + + public function testPreservedAreAppliedByDelete() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('delete')->once()->with('delete from "users"', []); + $builder->beforeQuery(function ($builder) { + $builder->from('users'); + }); + $builder->delete(); + } + + public function testPreservedAreAppliedByTruncate() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('statement')->once()->with('truncate table "users"', []); + $builder->beforeQuery(function ($builder) { + $builder->from('users'); + }); + $builder->truncate(); + } + + public function testPreservedAreAppliedByExists() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with('select exists(select * from "users") as "exists"', [], true); + $builder->beforeQuery(function ($builder) { + $builder->from('users'); + }); + $builder->exists(); + } + public function testPostgresInsertGetId() { $builder = $this->getPostgresBuilder();