diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index 289770375f94..5f2ec2dab7b9 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -210,14 +210,18 @@ public function withCount($relations) $query->callScope($constraints); - $query->mergeConstraintsFrom($relation->getQuery()); + $query = $query->mergeConstraintsFrom($relation->getQuery())->toBase(); + + if (count($query->columns) > 1) { + $query->columns = [$query->columns[0]]; + } // Finally we will add the proper result column alias to the query and run the subselect // statement against the query builder. Then we will return the builder instance back // to the developer for further constraint chaining that needs to take place on it. $column = $alias ?? Str::snake($name.'_count'); - $this->selectSub($query->toBase(), $column); + $this->selectSub($query, $column); } return $this; diff --git a/src/Illuminate/Database/Eloquent/FactoryBuilder.php b/src/Illuminate/Database/Eloquent/FactoryBuilder.php index 0fc522c1a109..7f4eccaa9da8 100644 --- a/src/Illuminate/Database/Eloquent/FactoryBuilder.php +++ b/src/Illuminate/Database/Eloquent/FactoryBuilder.php @@ -313,7 +313,7 @@ protected function stateAttributes($state, array $attributes) protected function expandAttributes(array $attributes) { foreach ($attributes as &$attribute) { - if (is_callable($attribute) && ! is_string($attribute)) { + if (is_callable($attribute) && ! is_string($attribute) && ! is_array($attribute)) { $attribute = $attribute($attributes); } diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 80c59cbd4b72..c6243e82ef5b 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -1372,7 +1372,7 @@ public function resolveRouteBinding($value) */ public function getForeignKey() { - return Str::snake(class_basename($this)).'_'.$this->primaryKey; + return Str::snake(class_basename($this)).'_'.$this->getKeyName(); } /** diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 2bbddfd066f9..1025915ed919 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -279,8 +279,6 @@ public function selectSub($query, $as) protected function parseSubSelect($query) { if ($query instanceof self) { - $query->columns = [$query->columns[0]]; - return [$query->toSql(), $query->getBindings()]; } elseif (is_string($query)) { return [$query, []]; diff --git a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php index 8dff604ae6c3..8f4f81d0fcee 100755 --- a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php @@ -9,6 +9,13 @@ class MySqlGrammar extends Grammar { + /** + * The grammar specific operators. + * + * @var array + */ + protected $operators = ['sounds like']; + /** * The components that make up a select clause. * diff --git a/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php b/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php index 300305564ebe..e6f70a098251 100644 --- a/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php +++ b/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php @@ -16,6 +16,8 @@ protected function filter($data) { $index = -1; + $numericKeys = array_values($data) === $data; + foreach ($data as $key => $value) { $index++; @@ -26,16 +28,7 @@ protected function filter($data) } if (is_numeric($key) && $value instanceof MergeValue) { - return $this->merge($data, $index, $this->filter($value->data)); - } - - if (($value instanceof PotentiallyMissing && $value->isMissing()) || - ($value instanceof self && - $value->resource instanceof PotentiallyMissing && - $value->isMissing())) { - unset($data[$key]); - - $index--; + return $this->merge($data, $index, $this->filter($value->data), $numericKeys); } if ($value instanceof self && is_null($value->resource)) { @@ -43,7 +36,7 @@ protected function filter($data) } } - return $data; + return $this->removeMissingValues($data, $numericKeys); } /** @@ -52,20 +45,42 @@ protected function filter($data) * @param array $data * @param int $index * @param array $merge + * @param bool $numericKeys * @return array */ - protected function merge($data, $index, $merge) + protected function merge($data, $index, $merge, $numericKeys) { - if (array_values($data) === $data) { - return array_merge( + if ($numericKeys) { + return $this->removeMissingValues(array_merge( array_merge(array_slice($data, 0, $index, true), $merge), - $this->filter(array_slice($data, $index + 1, null, true)) - ); + $this->filter(array_values(array_slice($data, $index + 1, null, true))) + ), $numericKeys); } - return array_slice($data, 0, $index, true) + + return $this->removeMissingValues(array_slice($data, 0, $index, true) + $merge + - $this->filter(array_slice($data, $index + 1, null, true)); + $this->filter(array_slice($data, $index + 1, null, true))); + } + + /** + * Remove the missing values from the filtered data. + * + * @param array $data + * @param bool $numericKeys + * @return array + */ + protected function removeMissingValues($data, $numericKeys = false) + { + foreach ($data as $key => $value) { + if (($value instanceof PotentiallyMissing && $value->isMissing()) || + ($value instanceof self && + $value->resource instanceof PotentiallyMissing && + $value->isMissing())) { + unset($data[$key]); + } + } + + return $numericKeys ? array_values($data) : $data; } /** diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesHelpers.php b/src/Illuminate/View/Compilers/Concerns/CompilesHelpers.php index 4edd61f475ee..979cadc560cf 100644 --- a/src/Illuminate/View/Compilers/Concerns/CompilesHelpers.php +++ b/src/Illuminate/View/Compilers/Concerns/CompilesHelpers.php @@ -25,6 +25,17 @@ protected function compileDd($arguments) return ""; } + /** + * Compile the "dump" statements into valid PHP. + * + * @param string $arguments + * @return string + */ + protected function compileDump($arguments) + { + return ""; + } + /** * Compile the method statements into valid PHP. * diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index ac5b228302e2..02ca507f9f38 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -1449,10 +1449,11 @@ public function testAggregateWithSubSelect() return $results; }); $builder->from('users')->selectSub(function ($query) { - $query->from('posts')->select('foo')->where('title', 'foo'); + $query->from('posts')->select('foo', 'bar')->where('title', 'foo'); }, 'post'); $count = $builder->count(); $this->assertEquals(1, $count); + $this->assertEquals('(select "foo", "bar" from "posts" where "title" = ?) as "post"', $builder->columns[0]->getValue()); $this->assertEquals(['foo'], $builder->getBindings()); } @@ -1940,6 +1941,14 @@ public function testSqlServerLimitsAndOffsets() $this->assertEquals('select * from (select *, row_number() over (order by [email] desc) as row_num from [users]) as temp_table where row_num between 11 and 20', $builder->toSql()); } + public function testMySqlSoundsLikeOperator() + { + $builder = $this->getMySqlBuilder(); + $builder->select('*')->from('users')->where('name', 'sounds like', 'John Doe'); + $this->assertEquals('select * from `users` where `name` sounds like ?', $builder->toSql()); + $this->assertEquals(['John Doe'], $builder->getBindings()); + } + public function testMergeWheresCanMergeWheresAndBindings() { $builder = $this->getBuilder(); diff --git a/tests/Integration/Database/EloquentFactoryBuilderTest.php b/tests/Integration/Database/EloquentFactoryBuilderTest.php index 3553b02d9436..395b652d5e76 100644 --- a/tests/Integration/Database/EloquentFactoryBuilderTest.php +++ b/tests/Integration/Database/EloquentFactoryBuilderTest.php @@ -42,6 +42,7 @@ protected function getEnvironmentSetUp($app) return [ 'name' => $faker->name, 'status' => 'active', + 'tags' => ['Storage', 'Data'], 'user_id' => function () { return factory(FactoryBuildableUser::class)->create()->id; }, @@ -80,6 +81,7 @@ public function setUp() Schema::create('servers', function ($table) { $table->increments('id'); $table->string('name'); + $table->string('tags'); $table->integer('user_id'); $table->string('status'); }); @@ -134,6 +136,7 @@ public function creating_models_with_callable_states() $callableServer = factory(FactoryBuildableServer::class)->states('callable')->create(); $this->assertEquals('active', $server->status); + $this->assertEquals(['Storage', 'Data'], $server->tags); $this->assertEquals('callable', $callableServer->status); } @@ -208,6 +211,7 @@ class FactoryBuildableServer extends Model public $table = 'servers'; public $timestamps = false; protected $guarded = ['id']; + public $casts = ['tags' => 'array']; public function user() { diff --git a/tests/Integration/Http/ResourceTest.php b/tests/Integration/Http/ResourceTest.php index f8d2f1c0d26d..5b52520ac61d 100644 --- a/tests/Integration/Http/ResourceTest.php +++ b/tests/Integration/Http/ResourceTest.php @@ -4,9 +4,11 @@ use Orchestra\Testbench\TestCase; use Illuminate\Support\Facades\Route; +use Illuminate\Http\Resources\MergeValue; use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Tests\Integration\Http\Fixtures\Post; use Illuminate\Tests\Integration\Http\Fixtures\Author; +use Illuminate\Http\Resources\ConditionallyLoadsAttributes; use Illuminate\Tests\Integration\Http\Fixtures\PostResource; use Illuminate\Tests\Integration\Http\Fixtures\Subscription; use Illuminate\Tests\Integration\Http\Fixtures\PostCollectionResource; @@ -521,4 +523,139 @@ public function test_original_on_response_is_collection_of_model_when_collection $this->assertTrue($response->getOriginalContent()->contains($post)); }); } + + public function test_leading_merge_value_is_merged_correctly() + { + $filter = new class { + use ConditionallyLoadsAttributes; + + public function work() + { + return $this->filter([ + new MergeValue(['First', 'Second']), + 'Taylor', + 'Mohamed', + new MergeValue(['Adam', 'Matt']), + 'Jeffrey', + new MergeValue(['Abigail', 'Lydia']), + ]); + } + }; + + $results = $filter->work(); + + $this->assertEquals([ + 'First', 'Second', 'Taylor', 'Mohamed', 'Adam', 'Matt', 'Jeffrey', 'Abigail', 'Lydia', + ], $results); + } + + public function test_merge_values_may_be_missing() + { + $filter = new class { + use ConditionallyLoadsAttributes; + + public function work() + { + return $this->filter([ + new MergeValue(['First', 'Second']), + 'Taylor', + 'Mohamed', + $this->mergeWhen(false, ['Adam', 'Matt']), + 'Jeffrey', + new MergeValue(['Abigail', 'Lydia']), + ]); + } + }; + + $results = $filter->work(); + + $this->assertEquals([ + 'First', 'Second', 'Taylor', 'Mohamed', 'Jeffrey', 'Abigail', 'Lydia', + ], $results); + } + + public function test_initial_merge_values_may_be_missing() + { + $filter = new class { + use ConditionallyLoadsAttributes; + + public function work() + { + return $this->filter([ + $this->mergeWhen(false, ['First', 'Second']), + 'Taylor', + 'Mohamed', + $this->mergeWhen(true, ['Adam', 'Matt']), + 'Jeffrey', + new MergeValue(['Abigail', 'Lydia']), + ]); + } + }; + + $results = $filter->work(); + + $this->assertEquals([ + 'Taylor', 'Mohamed', 'Adam', 'Matt', 'Jeffrey', 'Abigail', 'Lydia', + ], $results); + } + + public function test_all_merge_values_may_be_missing() + { + $filter = new class { + use ConditionallyLoadsAttributes; + + public function work() + { + return $this->filter([ + $this->mergeWhen(false, ['First', 'Second']), + 'Taylor', + 'Mohamed', + $this->mergeWhen(false, ['Adam', 'Matt']), + 'Jeffrey', + $this->mergeWhen(false, (['Abigail', 'Lydia'])), + ]); + } + }; + + $results = $filter->work(); + + $this->assertEquals([ + 'Taylor', 'Mohamed', 'Jeffrey', + ], $results); + } + + public function test_nested_merges() + { + $filter = new class { + use ConditionallyLoadsAttributes; + + public function work() + { + return $this->filter([ + $this->mergeWhen(true, [['Something']]), + [ + $this->mergeWhen(true, ['First', $this->mergeWhen(true, ['Second'])]), + 'Third', + ], + [ + 'Fourth', + ], + ]); + } + }; + + $results = $filter->work(); + + $this->assertEquals([ + [ + 'Something', + ], + [ + 'First', 'Second', 'Third', + ], + [ + 'Fourth', + ], + ], $results); + } } diff --git a/tests/View/Blade/BladeHelpersTest.php b/tests/View/Blade/BladeHelpersTest.php index c22e6508637d..9df79cec08a7 100644 --- a/tests/View/Blade/BladeHelpersTest.php +++ b/tests/View/Blade/BladeHelpersTest.php @@ -10,5 +10,6 @@ public function testEchosAreCompiled() $this->assertEquals('', $this->compiler->compileString("@method('patch')")); $this->assertEquals('', $this->compiler->compileString('@dd($var1)')); $this->assertEquals('', $this->compiler->compileString('@dd($var1, $var2)')); + $this->assertEquals('', $this->compiler->compileString('@dump($var1, $var2)')); } }