From df67f064690857d76ada15f98c528c4ac77a3ec3 Mon Sep 17 00:00:00 2001 From: Fred Bradley Date: Wed, 7 Apr 2021 10:10:14 +0100 Subject: [PATCH 01/31] Allow to retry jobs by queue name --- src/Illuminate/Queue/Console/RetryCommand.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Illuminate/Queue/Console/RetryCommand.php b/src/Illuminate/Queue/Console/RetryCommand.php index 2f651b60d098..13817c2b7487 100644 --- a/src/Illuminate/Queue/Console/RetryCommand.php +++ b/src/Illuminate/Queue/Console/RetryCommand.php @@ -18,6 +18,7 @@ class RetryCommand extends Command */ protected $signature = 'queue:retry {id?* : The ID of the failed job or "all" to retry all jobs} + {--queue= : Name of the queue to be retried} {--range=* : Range of job IDs (numeric) to be retried}'; /** @@ -62,6 +63,16 @@ protected function getJobIds() return Arr::pluck($this->laravel['queue.failer']->all(), 'id'); } + if ($queue = $this->option('queue')) { + $ids = collect($this->laravel['queue.failer']->all())->where('queue', $queue)->pluck('id')->toArray(); + + if (count($ids)===0) { + $this->error("Unable to find failed jobs in a queue named [{$queue}]."); + } + + return $ids; + } + if ($ranges = (array) $this->option('range')) { $ids = array_merge($ids, $this->getJobIdsByRanges($ranges)); } From be679d3c0e6da7ab161c61993feddf4eaeca4119 Mon Sep 17 00:00:00 2001 From: Fred Bradley Date: Wed, 7 Apr 2021 10:34:41 +0100 Subject: [PATCH 02/31] refactor: fix style spacing --- src/Illuminate/Queue/Console/RetryCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Queue/Console/RetryCommand.php b/src/Illuminate/Queue/Console/RetryCommand.php index 13817c2b7487..a1452dbf2c40 100644 --- a/src/Illuminate/Queue/Console/RetryCommand.php +++ b/src/Illuminate/Queue/Console/RetryCommand.php @@ -66,7 +66,7 @@ protected function getJobIds() if ($queue = $this->option('queue')) { $ids = collect($this->laravel['queue.failer']->all())->where('queue', $queue)->pluck('id')->toArray(); - if (count($ids)===0) { + if (count($ids) === 0) { $this->error("Unable to find failed jobs in a queue named [{$queue}]."); } From f2d9b595e51d564c5e1390eb42438c632e0daf36 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 7 Apr 2021 08:09:33 -0500 Subject: [PATCH 03/31] formatting --- src/Illuminate/Queue/Console/RetryCommand.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Queue/Console/RetryCommand.php b/src/Illuminate/Queue/Console/RetryCommand.php index a1452dbf2c40..f32caf49fd85 100644 --- a/src/Illuminate/Queue/Console/RetryCommand.php +++ b/src/Illuminate/Queue/Console/RetryCommand.php @@ -18,7 +18,7 @@ class RetryCommand extends Command */ protected $signature = 'queue:retry {id?* : The ID of the failed job or "all" to retry all jobs} - {--queue= : Name of the queue to be retried} + {--queue= : Retry all of the failed jobs for the specified queue} {--range=* : Range of job IDs (numeric) to be retried}'; /** @@ -64,10 +64,13 @@ protected function getJobIds() } if ($queue = $this->option('queue')) { - $ids = collect($this->laravel['queue.failer']->all())->where('queue', $queue)->pluck('id')->toArray(); + $ids = collect($this->laravel['queue.failer']->all()) + ->where('queue', $queue) + ->pluck('id') + ->toArray(); if (count($ids) === 0) { - $this->error("Unable to find failed jobs in a queue named [{$queue}]."); + $this->error("Unable to find failed jobs for queue [{$queue}]."); } return $ids; From c351a309f1a02098f9a7ee24a8a402e9ce06fead Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 7 Apr 2021 08:11:23 -0500 Subject: [PATCH 04/31] formatting --- src/Illuminate/Queue/Console/RetryCommand.php | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Queue/Console/RetryCommand.php b/src/Illuminate/Queue/Console/RetryCommand.php index f32caf49fd85..212883fecdcb 100644 --- a/src/Illuminate/Queue/Console/RetryCommand.php +++ b/src/Illuminate/Queue/Console/RetryCommand.php @@ -64,16 +64,7 @@ protected function getJobIds() } if ($queue = $this->option('queue')) { - $ids = collect($this->laravel['queue.failer']->all()) - ->where('queue', $queue) - ->pluck('id') - ->toArray(); - - if (count($ids) === 0) { - $this->error("Unable to find failed jobs for queue [{$queue}]."); - } - - return $ids; + return $this->getJobIdsByQueue($queue); } if ($ranges = (array) $this->option('range')) { @@ -83,6 +74,26 @@ protected function getJobIds() return array_values(array_filter(array_unique($ids))); } + /** + * Get the job IDs by queue, if applicable. + * + * @param string $queue + * @return array + */ + protected function getJobIdsByQueue($queue) + { + $ids = collect($this->laravel['queue.failer']->all()) + ->where('queue', $queue) + ->pluck('id') + ->toArray(); + + if (count($ids) === 0) { + $this->error("Unable to find failed jobs for queue [{$queue}]."); + } + + return $ids; + } + /** * Get the job IDs ranges, if applicable. * From 42102589bc7f7b8533ee1b815ef0cc18017d4e45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rihards=20=C5=A0=C4=8Deredins?= Date: Thu, 8 Apr 2021 15:38:18 +0300 Subject: [PATCH 05/31] Add more messages for detecting lost connection (happens during managed PostgreSQL upgrade on DigitalOcean) (#36911) --- src/Illuminate/Database/DetectsLostConnections.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index 191eefedc891..93be53b2fdc9 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -52,6 +52,8 @@ protected function causedByLostConnection(Throwable $e) 'Temporary failure in name resolution', 'SSL: Broken pipe', 'SQLSTATE[08S01]: Communication link failure', + 'SQLSTATE[08006] [7] could not connect to server: Connection refused Is the server running on host', + 'SQLSTATE[HY000]: General error: 7 SSL SYSCALL error: No route to host', ]); } } From 5e0ef03e9fd664a31d0150df080df8e6549e2ef8 Mon Sep 17 00:00:00 2001 From: Lachlan Krautz Date: Thu, 8 Apr 2021 22:42:49 +1000 Subject: [PATCH 06/31] [8.x] Model::delete throw LogicException not Exception (#36914) * Throw LogicException instead of Exception on delete model with missing primary key * Update Model.php Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Eloquent/Model.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 62fcd058e258..245a103bce40 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -3,7 +3,6 @@ namespace Illuminate\Database\Eloquent; use ArrayAccess; -use Exception; use Illuminate\Contracts\Queue\QueueableCollection; use Illuminate\Contracts\Queue\QueueableEntity; use Illuminate\Contracts\Routing\UrlRoutable; @@ -20,6 +19,7 @@ use Illuminate\Support\Str; use Illuminate\Support\Traits\ForwardsCalls; use JsonSerializable; +use LogicException; abstract class Model implements Arrayable, ArrayAccess, Jsonable, JsonSerializable, QueueableEntity, UrlRoutable { @@ -1097,14 +1097,14 @@ public static function destroy($ids) * * @return bool|null * - * @throws \Exception + * @throws \LogicException */ public function delete() { $this->mergeAttributesFromClassCasts(); if (is_null($this->getKeyName())) { - throw new Exception('No primary key defined on model.'); + throw new LogicException('No primary key defined on model.'); } // If the model doesn't exist, there is nothing to delete so we'll just return From 94fd9662033b9b7554cb3ba96fbafcc44fc4f7b4 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Thu, 8 Apr 2021 21:58:09 +0200 Subject: [PATCH 07/31] Allow testing of Blade components that return closures (#36919) --- .../Foundation/Testing/Concerns/InteractsWithViews.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithViews.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithViews.php index 574009a68f95..faa6c64a367e 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithViews.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithViews.php @@ -2,6 +2,7 @@ namespace Illuminate\Foundation\Testing\Concerns; +use Closure; use Illuminate\Support\Facades\View as ViewFacade; use Illuminate\Support\MessageBag; use Illuminate\Support\Str; @@ -58,6 +59,10 @@ protected function component(string $componentClass, array $data = []) $view = $component->resolveView(); + if ($view instanceof Closure) { + $view = $view($data); + } + return $view instanceof View ? new TestView($view->with($component->data())) : new TestView(view($view, $component->data())); From a4084639eea11ac2649325f4cd9a7c267d01a878 Mon Sep 17 00:00:00 2001 From: Theraloss Date: Fri, 9 Apr 2021 14:45:47 +0200 Subject: [PATCH 08/31] add eloquent builder to param type of selectSub (#36926) --- src/Illuminate/Database/Query/Builder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 710d0a3a568c..befc8a8cfa33 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -244,7 +244,7 @@ public function select($columns = ['*']) /** * Add a subselect expression to the query. * - * @param \Closure|\Illuminate\Database\Query\Builder|string $query + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as * @return $this * From bf3d5e319f0bd928a493222adb1acd9d23c1db68 Mon Sep 17 00:00:00 2001 From: Propaganistas Date: Fri, 9 Apr 2021 14:48:38 +0200 Subject: [PATCH 09/31] [8.x] Fix clone() on EloquentBuilder (#36924) * Support clone() on EloquentBuilder * Update Builder.php Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Eloquent/Builder.php | 10 ++++++++++ tests/Database/DatabaseEloquentBuilderTest.php | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index ca84d06cdbc1..a8f2fb87842c 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -1608,6 +1608,16 @@ protected static function registerMixin($mixin, $replace) } } + /** + * Clone the Eloquent query builder. + * + * @return static + */ + public function clone() + { + return clone $this; + } + /** * Force a clone of the underlying query builder when cloning. * diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 0809f8e0fe98..682545ff9b98 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -1497,6 +1497,18 @@ public function testWithCastsMethod() $builder->withCasts(['foo' => 'bar']); } + public function testClone() + { + $query = new BaseBuilder(m::mock(ConnectionInterface::class), new Grammar, m::mock(Processor::class)); + $builder = new Builder($query); + $builder->select('*')->from('users'); + $clone = $builder->clone()->where('email', 'foo'); + + $this->assertNotSame($builder, $clone); + $this->assertSame('select * from "users"', $builder->toSql()); + $this->assertSame('select * from "users" where "email" = ?', $clone->toSql()); + } + protected function mockConnectionForModel($model, $database) { $grammarClass = 'Illuminate\Database\Query\Grammars\\'.$database.'Grammar'; From 9311dafa61fe5e7fd1aaffa525eca3dd15992fef Mon Sep 17 00:00:00 2001 From: Thiago Barcala Date: Wed, 7 Apr 2021 21:27:52 +0200 Subject: [PATCH 10/31] Implement anonymous migrations --- .../Database/Migrations/Migrator.php | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index 093e41e78a43..b9bf18195a54 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -13,6 +13,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Str; +use ReflectionClass; use Symfony\Component\Console\Output\OutputInterface; class Migrator @@ -185,9 +186,8 @@ protected function runUp($file, $batch, $pretend) // First we will resolve a "real" instance of the migration class from this // migration file name. Once we have the instances we can run the actual // command such as "up" or "down", or we can just simulate the action. - $migration = $this->resolve( - $name = $this->getMigrationName($file) - ); + $migration = $this->resolvePath($file); + $name = $this->getMigrationName($file); if ($pretend) { return $this->pretendToRun($migration, 'up'); @@ -348,9 +348,8 @@ protected function runDown($file, $migration, $pretend) // First we will get the file name of the migration so we can resolve out an // instance of the migration. Once we get an instance we can either run a // pretend execution of the migration or we can run the real migration. - $instance = $this->resolve( - $name = $this->getMigrationName($file) - ); + $instance = $this->resolvePath($file); + $name = $this->getMigrationName($file); $this->note("Rolling back: {$name}"); @@ -411,7 +410,7 @@ protected function runMigration($migration, $method) protected function pretendToRun($migration, $method) { foreach ($this->getQueries($migration, $method) as $query) { - $name = get_class($migration); + $name = $this->getMigrationName((new ReflectionClass($migration))->getFileName()); $this->note("{$name}: {$query['query']}"); } @@ -448,11 +447,38 @@ protected function getQueries($migration, $method) */ public function resolve($file) { - $class = Str::studly(implode('_', array_slice(explode('_', $file), 4))); + $class = $this->getMigrationClass($file); return new $class; } + /** + * Resolve a migration instance from migration path. + * + * @param string $path + * @return object + */ + protected function resolvePath(string $path) + { + $class = $this->getMigrationClass($this->getMigrationName($path)); + if (class_exists($class)) { + return new $class; + } + + return $this->files->getRequire($path); + } + + /** + * Generate migration class name based on migration name. + * + * @param string $migrationName + * @return string + */ + protected function getMigrationClass(string $migrationName): string + { + return Str::studly(implode('_', array_slice(explode('_', $migrationName), 4))); + } + /** * Get all of the migration files in a given path. * From 9cdb5cc62a0a6c5c86464c62b0b1360f220a7994 Mon Sep 17 00:00:00 2001 From: Thiago Barcala Date: Wed, 7 Apr 2021 21:22:35 +0200 Subject: [PATCH 11/31] Extend MigratorTest --- tests/Integration/Migration/MigratorTest.php | 74 +++++++++++++++---- .../2015_10_04_000000_modify_people_table.php | 31 ++++++++ .../2016_10_04_000000_modify_people_table.php | 31 ++++++++ 3 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php create mode 100644 tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php diff --git a/tests/Integration/Migration/MigratorTest.php b/tests/Integration/Migration/MigratorTest.php index 50ee6f3cbd6b..9e685379bb2a 100644 --- a/tests/Integration/Migration/MigratorTest.php +++ b/tests/Integration/Migration/MigratorTest.php @@ -2,11 +2,29 @@ namespace Illuminate\Tests\Integration\Migration; +use Illuminate\Support\Facades\DB; +use Mockery; +use Mockery\Mock; use Orchestra\Testbench\TestCase; -use PDOException; +use Symfony\Component\Console\Output\OutputInterface; class MigratorTest extends TestCase { + /** + * @var Mock + */ + private $output; + + protected function setUp(): void + { + parent::setUp(); + + $this->output = Mockery::mock(OutputInterface::class); + $this->subject = $this->app->make('migrator'); + $this->subject->setOutput($this->output); + $this->subject->getRepository()->createRepository(); + } + protected function getEnvironmentSetUp($app) { $app['config']->set('app.debug', 'true'); @@ -19,25 +37,55 @@ protected function getEnvironmentSetUp($app) ]); } - public function testDontDisplayOutputWhenOutputObjectIsNotAvailable() + public function testMigrate() + { + $this->expectOutput('Migrating: 2014_10_12_000000_create_people_table'); + $this->expectOutput(Mockery::pattern('#Migrated: 2014_10_12_000000_create_people_table (.*)#')); + $this->expectOutput('Migrating: 2015_10_04_000000_modify_people_table'); + $this->expectOutput(Mockery::pattern('#Migrated: 2015_10_04_000000_modify_people_table (.*)#')); + $this->expectOutput('Migrating: 2016_10_04_000000_modify_people_table'); + $this->expectOutput(Mockery::pattern('#Migrated: 2016_10_04_000000_modify_people_table (.*)#')); + + $this->subject->run([__DIR__.'/fixtures']); + + self::assertTrue(DB::getSchemaBuilder()->hasTable('people')); + self::assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'first_name')); + self::assertTrue(DB::getSchemaBuilder()->hasColumn('people', 'last_name')); + } + + public function testRollback() { - $migrator = $this->app->make('migrator'); + $this->getConnection()->statement('CREATE TABLE people(id INT, first_name VARCHAR, last_name VARCHAR);'); + $this->subject->getRepository()->log('2014_10_12_000000_create_people_table', 1); + $this->subject->getRepository()->log('2015_10_04_000000_modify_people_table', 1); + $this->subject->getRepository()->log('2016_10_04_000000_modify_people_table', 1); - $migrator->getRepository()->createRepository(); + $this->expectOutput('Rolling back: 2016_10_04_000000_modify_people_table'); + $this->expectOutput(Mockery::pattern('#Rolled back: 2016_10_04_000000_modify_people_table (.*)#')); + $this->expectOutput('Rolling back: 2015_10_04_000000_modify_people_table'); + $this->expectOutput(Mockery::pattern('#Rolled back: 2015_10_04_000000_modify_people_table (.*)#')); + $this->expectOutput('Rolling back: 2014_10_12_000000_create_people_table'); + $this->expectOutput(Mockery::pattern('#Rolled back: 2014_10_12_000000_create_people_table (.*)#')); - $migrator->run([__DIR__.'/fixtures']); + $this->subject->rollback([__DIR__.'/fixtures']); - $this->assertTrue($this->tableExists('people')); + self::assertFalse(DB::getSchemaBuilder()->hasTable('people')); } - private function tableExists($table): bool + public function testPretendMigrate() { - try { - $this->app->make('db')->select("SELECT COUNT(*) FROM $table"); - } catch (PDOException $e) { - return false; - } + $this->expectOutput('2014_10_12_000000_create_people_table: create table "people" ("id" integer not null primary key autoincrement, "name" varchar not null, "email" varchar not null, "password" varchar not null, "remember_token" varchar, "created_at" datetime, "updated_at" datetime)'); + $this->expectOutput('2014_10_12_000000_create_people_table: create unique index "people_email_unique" on "people" ("email")'); + $this->expectOutput('2015_10_04_000000_modify_people_table: alter table "people" add column "first_name" varchar'); + $this->expectOutput('2016_10_04_000000_modify_people_table: alter table "people" add column "last_name" varchar'); + + $this->subject->run([__DIR__.'/fixtures'], ['pretend' => true]); - return true; + self::assertFalse(DB::getSchemaBuilder()->hasTable('people')); + } + + private function expectOutput($argument): void + { + $this->output->shouldReceive('writeln')->once()->with($argument); } } diff --git a/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php b/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php new file mode 100644 index 000000000000..88ac706cd12a --- /dev/null +++ b/tests/Integration/Migration/fixtures/2015_10_04_000000_modify_people_table.php @@ -0,0 +1,31 @@ +string('first_name')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('people', function (Blueprint $table) { + $table->dropColumn('first_name'); + }); + } +}; diff --git a/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php b/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php new file mode 100644 index 000000000000..6492b6d7f55a --- /dev/null +++ b/tests/Integration/Migration/fixtures/2016_10_04_000000_modify_people_table.php @@ -0,0 +1,31 @@ +string('last_name')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('people', function (Blueprint $table) { + $table->dropColumn('last_name'); + }); + } +}; From 64272b49e9426eec8526a097471bcb779e3ec001 Mon Sep 17 00:00:00 2001 From: Thiago Barcala Date: Sun, 11 Apr 2021 08:38:40 +0200 Subject: [PATCH 12/31] Display migration class name in pretendToRun Due to backward compatibility, classic migrations (non-anonymous) should have their class name displayed while running migrations with --pretend, instead of the migration name, like anywhere else in the code. --- src/Illuminate/Database/Migrations/Migrator.php | 7 ++++++- tests/Integration/Migration/MigratorTest.php | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index b9bf18195a54..b51db2a142c5 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -410,7 +410,12 @@ protected function runMigration($migration, $method) protected function pretendToRun($migration, $method) { foreach ($this->getQueries($migration, $method) as $query) { - $name = $this->getMigrationName((new ReflectionClass($migration))->getFileName()); + $name = get_class($migration); + + $reflectionClass = new ReflectionClass($migration); + if ($reflectionClass->isAnonymous()) { + $name = $this->getMigrationName($reflectionClass->getFileName()); + } $this->note("{$name}: {$query['query']}"); } diff --git a/tests/Integration/Migration/MigratorTest.php b/tests/Integration/Migration/MigratorTest.php index 9e685379bb2a..08dd9c97862f 100644 --- a/tests/Integration/Migration/MigratorTest.php +++ b/tests/Integration/Migration/MigratorTest.php @@ -74,8 +74,8 @@ public function testRollback() public function testPretendMigrate() { - $this->expectOutput('2014_10_12_000000_create_people_table: create table "people" ("id" integer not null primary key autoincrement, "name" varchar not null, "email" varchar not null, "password" varchar not null, "remember_token" varchar, "created_at" datetime, "updated_at" datetime)'); - $this->expectOutput('2014_10_12_000000_create_people_table: create unique index "people_email_unique" on "people" ("email")'); + $this->expectOutput('CreatePeopleTable: create table "people" ("id" integer not null primary key autoincrement, "name" varchar not null, "email" varchar not null, "password" varchar not null, "remember_token" varchar, "created_at" datetime, "updated_at" datetime)'); + $this->expectOutput('CreatePeopleTable: create unique index "people_email_unique" on "people" ("email")'); $this->expectOutput('2015_10_04_000000_modify_people_table: alter table "people" add column "first_name" varchar'); $this->expectOutput('2016_10_04_000000_modify_people_table: alter table "people" add column "last_name" varchar'); From ef99cffd4938759cecfe2cb19888e9ff3e942b87 Mon Sep 17 00:00:00 2001 From: Felix Schmid Date: Sun, 11 Apr 2021 18:12:42 +0200 Subject: [PATCH 13/31] [8.x] Add more readable `missing` method to Session\Store (#36937) * Add missing helper for session * Update Store.php Co-authored-by: Taylor Otwell --- src/Illuminate/Session/Store.php | 11 +++++++++++ tests/Session/SessionStoreTest.php | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Illuminate/Session/Store.php b/src/Illuminate/Session/Store.php index ff2a3b966a20..151e8b6330b5 100755 --- a/src/Illuminate/Session/Store.php +++ b/src/Illuminate/Session/Store.php @@ -193,6 +193,17 @@ public function exists($key) }); } + /** + * Determine if the given key is missing from the session data. + * + * @param string|array $key + * @return bool + */ + public function missing($key) + { + return ! $this->exists($key); + } + /** * Checks if a key is present and not null. * diff --git a/tests/Session/SessionStoreTest.php b/tests/Session/SessionStoreTest.php index 5871cc657a85..188bdce4d89f 100644 --- a/tests/Session/SessionStoreTest.php +++ b/tests/Session/SessionStoreTest.php @@ -452,6 +452,22 @@ public function testKeyExists() $this->assertFalse($session->exists(['hulk.two'])); } + public function testKeyMissing() + { + $session = $this->getSession(); + $session->put('foo', 'bar'); + $this->assertFalse($session->missing('foo')); + $session->put('baz', null); + $session->put('hulk', ['one' => true]); + $this->assertFalse($session->has('baz')); + $this->assertFalse($session->missing('baz')); + $this->assertTrue($session->missing('bogus')); + $this->assertFalse($session->missing(['foo', 'baz'])); + $this->assertTrue($session->missing(['foo', 'baz', 'bogus'])); + $this->assertFalse($session->missing(['hulk.one'])); + $this->assertTrue($session->missing(['hulk.two'])); + } + public function testRememberMethodCallsPutAndReturnsDefault() { $session = $this->getSession(); From 8f2d3ef57d4d13de850181e5a253b14923893909 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 12 Apr 2021 09:23:29 +1000 Subject: [PATCH 14/31] Replace deprecated functions --- src/Illuminate/Http/Client/Factory.php | 6 ++++-- src/Illuminate/Http/Client/RequestException.php | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index 4db3e1fa98a0..83fd71fd0ae0 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -3,7 +3,7 @@ namespace Illuminate\Http\Client; use Closure; -use function GuzzleHttp\Promise\promise_for; +use GuzzleHttp\Promise\Create; use GuzzleHttp\Psr7\Response as Psr7Response; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; @@ -34,6 +34,8 @@ * @method \Illuminate\Http\Client\PendingRequest withoutVerifying() * @method \Illuminate\Http\Client\PendingRequest dump() * @method \Illuminate\Http\Client\PendingRequest dd() + * @method \Illuminate\Http\Client\PendingRequest async() + * @method \Illuminate\Http\Client\Pool pool() * @method \Illuminate\Http\Client\Response delete(string $url, array $data = []) * @method \Illuminate\Http\Client\Response get(string $url, array $query = []) * @method \Illuminate\Http\Client\Response head(string $url, array $query = []) @@ -104,7 +106,7 @@ public static function response($body = null, $status = 200, $headers = []) $headers['Content-Type'] = 'application/json'; } - return promise_for(new Psr7Response($status, $headers, $body)); + return Create::promiseFor(new Psr7Response($status, $headers, $body)); } /** diff --git a/src/Illuminate/Http/Client/RequestException.php b/src/Illuminate/Http/Client/RequestException.php index fe2ea8be4252..5dd37e306ebc 100644 --- a/src/Illuminate/Http/Client/RequestException.php +++ b/src/Illuminate/Http/Client/RequestException.php @@ -2,6 +2,8 @@ namespace Illuminate\Http\Client; +use GuzzleHttp\Psr7\Message; + class RequestException extends HttpClientException { /** @@ -34,7 +36,7 @@ protected function prepareMessage(Response $response) { $message = "HTTP request returned status code {$response->status()}"; - $summary = \GuzzleHttp\Psr7\get_message_body_summary($response->toPsrResponse()); + $summary = Message::bodySummary($response->toPsrResponse()); return is_null($summary) ? $message : $message .= ":\n{$summary}\n"; } From f30af7fd98df98b843e9fc21525c4380901eb4a4 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 12 Apr 2021 09:25:34 +1000 Subject: [PATCH 15/31] Enable concurrency of asynchronous requests --- src/Illuminate/Http/Client/PendingRequest.php | 157 ++++++++++++++++-- src/Illuminate/Http/Client/Pool.php | 84 ++++++++++ 2 files changed, 230 insertions(+), 11 deletions(-) create mode 100644 src/Illuminate/Http/Client/Pool.php diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 5801bc0b70f0..79be9399c874 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -5,10 +5,13 @@ use GuzzleHttp\Client; use GuzzleHttp\Cookie\CookieJar; use GuzzleHttp\Exception\ConnectException; +use GuzzleHttp\Exception\RequestException; +use GuzzleHttp\Exception\TransferException; use GuzzleHttp\HandlerStack; use Illuminate\Support\Collection; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; +use Psr\Http\Message\MessageInterface; use Symfony\Component\VarDumper\VarDumper; class PendingRequest @@ -22,6 +25,13 @@ class PendingRequest */ protected $factory; + /** + * The client instance. + * + * @var \GuzzleHttp\Client + */ + protected $client; + /** * The base URL for the request. * @@ -106,6 +116,20 @@ class PendingRequest */ protected $middleware; + /** + * Whether the requests should be asynchronous. + * + * @var bool + */ + protected $async = false; + + /** + * The pending request promise. + * + * @var \GuzzleHttp\Promise\PromiseInterface + */ + protected $promise; + /** * Create a new HTTP Client instance. * @@ -601,18 +625,14 @@ public function send(string $method, string $url, array $options = []) [$this->pendingBody, $this->pendingFiles] = [null, []]; + if ($this->async) { + return $this->makePromise($method, $url, $options); + } + return retry($this->tries ?? 1, function () use ($method, $url, $options) { try { - $laravelData = $this->parseRequestData($method, $url, $options); - - return tap(new Response($this->buildClient()->request($method, $url, $this->mergeOptions([ - 'laravel_data' => $laravelData, - 'on_stats' => function ($transferStats) { - $this->transferStats = $transferStats; - }, - ], $options))), function ($response) { - $response->cookies = $this->cookies; - $response->transferStats = $this->transferStats; + return tap(new Response($this->sendRequest($method, $url, $options)), function ($response) { + $this->populateResponse($response); if ($this->tries > 1 && ! $response->successful()) { $response->throw(); @@ -637,6 +657,49 @@ protected function parseMultipartBodyFormat(array $data) })->values()->all(); } + /** + * Send an asynchronous request to the given URL. + * + * @param string $method + * @param string $url + * @param array $options + * @return \GuzzleHttp\Promise\PromiseInterface + */ + protected function makePromise(string $method, string $url, array $options = []) + { + return $this->promise = $this->sendRequest($method, $url, $options) + ->then(function (MessageInterface $message) { + return $this->populateResponse(new Response($message)); + }) + ->otherwise(function (TransferException $e) { + return $e instanceof RequestException ? $this->populateResponse(new Response($e->getResponse())) : $e; + }); + } + + /** + * Send a request either synchronously or asynchronously. + * + * @param string $method + * @param string $url + * @param array $options + * @return \Psr\Http\Message\MessageInterface|\GuzzleHttp\Promise\PromiseInterface + * + * @throws \Exception + */ + protected function sendRequest(string $method, string $url, array $options = []) + { + $clientMethod = $this->async ? 'requestAsync' : 'request'; + + $laravelData = $this->parseRequestData($method, $url, $options); + + return $this->buildClient()->$clientMethod($method, $url, $this->mergeOptions([ + 'laravel_data' => $laravelData, + 'on_stats' => function ($transferStats) { + $this->transferStats = $transferStats; + }, + ], $options)); + } + /** * Get the request data as an array so that we can attach it to the request for convenient assertions. * @@ -664,6 +727,34 @@ protected function parseRequestData($method, $url, array $options) return $laravelData; } + /** + * Populate the given response with additional data. + * + * @param \Illuminate\Http\Client\Response $response + * @return \Illuminate\Http\Client\Response + */ + protected function populateResponse(Response $response) + { + $response->cookies = $this->cookies; + + $response->transferStats = $this->transferStats; + + return $response; + } + + /** + * Set the client instance. + * + * @param \GuzzleHttp\Client $client + * @return $this + */ + public function setClient(Client $client) + { + $this->client = $client; + + return $this; + } + /** * Build the Guzzle client. * @@ -671,7 +762,7 @@ protected function parseRequestData($method, $url, array $options) */ public function buildClient() { - return new Client([ + return $this->client = $this->client ?: new Client([ 'handler' => $this->buildHandlerStack(), 'cookies' => true, ]); @@ -826,4 +917,48 @@ public function stub($callback) return $this; } + + /** + * Toggle asynchronicity in requests. + * + * @param bool $async + * @return $this + */ + public function async(bool $async = true) + { + $this->async = $async; + + return $this; + } + + /** + * Send a pool of asynchronous requests concurrently + * + * @param callable $callback + * @return array + */ + public function pool(callable $callback) + { + $results = []; + + $requests = tap(new Pool($this->factory), $callback)->getRequests(); + + foreach ($requests as $key => $item) { + $results[$key] = $item instanceof static ? $item->getPromise()->wait() : $item->wait(); + } + + ksort($results); + + return $results; + } + + /** + * Retrieve the pending request promise. + * + * @return \GuzzleHttp\Promise\PromiseInterface|null + */ + public function getPromise() + { + return $this->promise; + } } diff --git a/src/Illuminate/Http/Client/Pool.php b/src/Illuminate/Http/Client/Pool.php new file mode 100644 index 000000000000..8b04f4300155 --- /dev/null +++ b/src/Illuminate/Http/Client/Pool.php @@ -0,0 +1,84 @@ +factory = $factory ?: new Factory(); + + $this->client = $this->factory->buildClient(); + } + + /** + * Add a request to the pool with a key. + * + * @param string $key + * @return \Illuminate\Http\Client\PendingRequest + */ + public function add(string $key) + { + return $this->pool[$key] = $this->asyncRequest(); + } + + /** + * Retrieve a new async pending request. + * + * @return \Illuminate\Http\Client\PendingRequest + */ + protected function asyncRequest() + { + // the same client instance needs to be shared across all async requests + return $this->factory->setClient($this->client)->async(); + } + + /** + * Retrieve the requests in the pool. + * + * @return array + */ + public function getRequests() + { + return $this->pool; + } + + /** + * Add a request to the pool with a numeric index. + * + * @param string $method + * @param array $parameters + * @return \Illuminate\Http\Client\PendingRequest + */ + public function __call($method, $parameters) + { + return $this->pool[] = $this->asyncRequest()->$method(...$parameters); + } +} From 529cf647a2e97b882661897accc2fdfaa3f568c0 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 12 Apr 2021 09:26:41 +1000 Subject: [PATCH 16/31] Add methods to make requests async and send a pool of requests --- src/Illuminate/Support/Facades/Http.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index 426d574789c5..51763b6caef0 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -32,6 +32,8 @@ * @method static \Illuminate\Http\Client\PendingRequest withoutVerifying() * @method static \Illuminate\Http\Client\PendingRequest dump() * @method static \Illuminate\Http\Client\PendingRequest dd() + * @method static \Illuminate\Http\Client\PendingRequest async() + * @method static \Illuminate\Http\Client\Pool pool() * @method static \Illuminate\Http\Client\Response delete(string $url, array $data = []) * @method static \Illuminate\Http\Client\Response get(string $url, array $query = []) * @method static \Illuminate\Http\Client\Response head(string $url, array $query = []) From 4cb083aea57e7c620f2d87feb3b259c55f1d9da4 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 12 Apr 2021 09:27:00 +1000 Subject: [PATCH 17/31] Create tests --- tests/Http/HttpClientTest.php | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 12d7a1806c21..473a5b4e06c5 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -2,8 +2,11 @@ namespace Illuminate\Tests\Http; +use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7\Response as Psr7Response; use Illuminate\Http\Client\Factory; +use Illuminate\Http\Client\PendingRequest; +use Illuminate\Http\Client\Pool; use Illuminate\Http\Client\Request; use Illuminate\Http\Client\RequestException; use Illuminate\Http\Client\Response; @@ -833,4 +836,70 @@ public function testResponseSequenceIsMacroable() $this->assertSame('yes!', $this->factory->fakeSequence()->customMethod()); } + + public function testRequestsCanBeAsync() + { + $request = new PendingRequest($this->factory); + + $promise = $request->async()->get('http://foo.com'); + + $this->assertInstanceOf(PromiseInterface::class, $promise); + + $this->assertSame($promise, $request->getPromise()); + } + + public function testClientCanBeSet() + { + $client = $this->factory->buildClient(); + + $request = new PendingRequest($this->factory); + + $this->assertNotSame($client, $request->buildClient()); + + $request->setClient($client); + + $this->assertSame($client, $request->buildClient()); + } + + public function testMultipleRequestsAreSentInThePool() + { + $this->factory->fake([ + '200.com' => $this->factory::response('', 200), + '400.com' => $this->factory::response('', 400), + '500.com' => $this->factory::response('', 500), + ]); + + $responses = $this->factory->pool(function (Pool $pool) { + return [ + $pool->get('200.com'), + $pool->get('400.com'), + $pool->get('500.com'), + ]; + }); + + $this->assertSame(200, $responses[0]->status()); + $this->assertSame(400, $responses[1]->status()); + $this->assertSame(500, $responses[2]->status()); + } + + public function testMultipleRequestsAreSentInThePoolWithKeys() + { + $this->factory->fake([ + '200.com' => $this->factory::response('', 200), + '400.com' => $this->factory::response('', 400), + '500.com' => $this->factory::response('', 500), + ]); + + $responses = $this->factory->pool(function (Pool $pool) { + return [ + $pool->add('test200')->get('200.com'), + $pool->add('test400')->get('400.com'), + $pool->add('test500')->get('500.com'), + ]; + }); + + $this->assertSame(200, $responses['test200']->status()); + $this->assertSame(400, $responses['test400']->status()); + $this->assertSame(500, $responses['test500']->status()); + } } From 0b4a04e77dc1198f0d64702a0e93ce203a680d37 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 12 Apr 2021 09:37:09 +1000 Subject: [PATCH 18/31] Revert deprecated function to support PHP 7.3 --- src/Illuminate/Http/Client/Factory.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index 83fd71fd0ae0..f9a507bf90a3 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -3,12 +3,13 @@ namespace Illuminate\Http\Client; use Closure; -use GuzzleHttp\Promise\Create; use GuzzleHttp\Psr7\Response as Psr7Response; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use PHPUnit\Framework\Assert as PHPUnit; +use function GuzzleHttp\Promise\promise_for; + /** * @method \Illuminate\Http\Client\PendingRequest accept(string $contentType) * @method \Illuminate\Http\Client\PendingRequest acceptJson() @@ -106,7 +107,7 @@ public static function response($body = null, $status = 200, $headers = []) $headers['Content-Type'] = 'application/json'; } - return Create::promiseFor(new Psr7Response($status, $headers, $body)); + return promise_for(new Psr7Response($status, $headers, $body)); } /** From 97bfb028613c9d46dca3ab8ddd4c43303d51d4e9 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 12 Apr 2021 09:50:10 +1000 Subject: [PATCH 19/31] Revert deprecated function to support PHP 7.3 --- src/Illuminate/Http/Client/RequestException.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Client/RequestException.php b/src/Illuminate/Http/Client/RequestException.php index 5dd37e306ebc..fe2ea8be4252 100644 --- a/src/Illuminate/Http/Client/RequestException.php +++ b/src/Illuminate/Http/Client/RequestException.php @@ -2,8 +2,6 @@ namespace Illuminate\Http\Client; -use GuzzleHttp\Psr7\Message; - class RequestException extends HttpClientException { /** @@ -36,7 +34,7 @@ protected function prepareMessage(Response $response) { $message = "HTTP request returned status code {$response->status()}"; - $summary = Message::bodySummary($response->toPsrResponse()); + $summary = \GuzzleHttp\Psr7\get_message_body_summary($response->toPsrResponse()); return is_null($summary) ? $message : $message .= ":\n{$summary}\n"; } From 589fe7968349b191395547b9a0c5044a22d4456f Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 12 Apr 2021 11:00:59 +1000 Subject: [PATCH 20/31] Reset use statements order --- src/Illuminate/Http/Client/Factory.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index f9a507bf90a3..6fc069d390b7 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -3,13 +3,12 @@ namespace Illuminate\Http\Client; use Closure; +use function GuzzleHttp\Promise\promise_for; use GuzzleHttp\Psr7\Response as Psr7Response; use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use PHPUnit\Framework\Assert as PHPUnit; -use function GuzzleHttp\Promise\promise_for; - /** * @method \Illuminate\Http\Client\PendingRequest accept(string $contentType) * @method \Illuminate\Http\Client\PendingRequest acceptJson() From ce69e55d79bd9ef15eb2c0013e889a1625ece816 Mon Sep 17 00:00:00 2001 From: Andrea Marco Sartori Date: Mon, 12 Apr 2021 11:29:15 +1000 Subject: [PATCH 21/31] Fix style --- src/Illuminate/Http/Client/PendingRequest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 79be9399c874..56f29dec2e5e 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -932,7 +932,7 @@ public function async(bool $async = true) } /** - * Send a pool of asynchronous requests concurrently + * Send a pool of asynchronous requests concurrently. * * @param callable $callback * @return array From d834187251af3fe31f7be92f9e5e3d196bc342d4 Mon Sep 17 00:00:00 2001 From: Bennett Treptow Date: Mon, 12 Apr 2021 07:23:06 -0500 Subject: [PATCH 22/31] [8.x] Add tinyText data type to Blueprint and to available database grammars (#36949) * Add tinyText to available grammars * Update test to also check that nullable() also works on the new type * Update style --- src/Illuminate/Database/Schema/Blueprint.php | 11 ++++ .../Database/Schema/Grammars/MySqlGrammar.php | 11 ++++ .../Schema/Grammars/PostgresGrammar.php | 11 ++++ .../Schema/Grammars/SQLiteGrammar.php | 11 ++++ .../Schema/Grammars/SqlServerGrammar.php | 11 ++++ .../Database/DatabaseSchemaBlueprintTest.php | 58 +++++++++++++++++++ 6 files changed, 113 insertions(+) diff --git a/src/Illuminate/Database/Schema/Blueprint.php b/src/Illuminate/Database/Schema/Blueprint.php index 622659995410..e2b968ab6bd4 100755 --- a/src/Illuminate/Database/Schema/Blueprint.php +++ b/src/Illuminate/Database/Schema/Blueprint.php @@ -677,6 +677,17 @@ public function string($column, $length = null) return $this->addColumn('string', $column, compact('length')); } + /** + * Create a new tiny text column on the table. + * + * @param string $column + * @return \Illuminate\Database\Schema\ColumnDefinition + */ + public function tinyText($column) + { + return $this->addColumn('tinyText', $column); + } + /** * Create a new text column on the table. * diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index dd53b9fe9bed..37df3337b040 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -490,6 +490,17 @@ protected function typeString(Fluent $column) return "varchar({$column->length})"; } + /** + * Create the column definition for a tiny text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyText(Fluent $column) + { + return 'tinytext'; + } + /** * Create the column definition for a text type. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index adaf21f90e4c..fb7005b09df3 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -472,6 +472,17 @@ protected function typeString(Fluent $column) return "varchar({$column->length})"; } + /** + * Create the column definition for a tiny text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyText(Fluent $column) + { + return 'varchar(255)'; + } + /** * Create the column definition for a text type. * diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 556d749e23b2..b7e406f578ef 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -432,6 +432,17 @@ protected function typeString(Fluent $column) return 'varchar'; } + /** + * Create the column definition for a tiny text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyText(Fluent $column) + { + return 'text'; + } + /** * Create the column definition for a text type. * diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index c3fc442e2368..b147628ec2f3 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -417,6 +417,17 @@ protected function typeString(Fluent $column) return "nvarchar({$column->length})"; } + /** + * Create the column definition for a tiny text type. + * + * @param \Illuminate\Support\Fluent $column + * @return string + */ + protected function typeTinyText(Fluent $column) + { + return 'nvarchar(255)'; + } + /** * Create the column definition for a text type. * diff --git a/tests/Database/DatabaseSchemaBlueprintTest.php b/tests/Database/DatabaseSchemaBlueprintTest.php index 94303415e8f4..5247df35c3ba 100755 --- a/tests/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Database/DatabaseSchemaBlueprintTest.php @@ -291,4 +291,62 @@ public function testGenerateRelationshipColumnWithUuidModel() 'alter table `posts` add `eloquent_model_uuid_stub_id` char(36) not null', ], $blueprint->toSql($connection, new MySqlGrammar)); } + + public function testTinyTextColumn() + { + $base = new Blueprint('posts', function ($table) { + $table->tinyText('note'); + }); + + $connection = m::mock(Connection::class); + + $blueprint = clone $base; + $this->assertEquals([ + 'alter table `posts` add `note` tinytext not null', + ], $blueprint->toSql($connection, new MySqlGrammar)); + + $blueprint = clone $base; + $this->assertEquals([ + 'alter table "posts" add column "note" text not null', + ], $blueprint->toSql($connection, new SQLiteGrammar)); + + $blueprint = clone $base; + $this->assertEquals([ + 'alter table "posts" add column "note" varchar(255) not null', + ], $blueprint->toSql($connection, new PostgresGrammar)); + + $blueprint = clone $base; + $this->assertEquals([ + 'alter table "posts" add "note" nvarchar(255) not null', + ], $blueprint->toSql($connection, new SqlServerGrammar)); + } + + public function testTinyTextNullableColumn() + { + $base = new Blueprint('posts', function ($table) { + $table->tinyText('note')->nullable(); + }); + + $connection = m::mock(Connection::class); + + $blueprint = clone $base; + $this->assertEquals([ + 'alter table `posts` add `note` tinytext null', + ], $blueprint->toSql($connection, new MySqlGrammar)); + + $blueprint = clone $base; + $this->assertEquals([ + 'alter table "posts" add column "note" text', + ], $blueprint->toSql($connection, new SQLiteGrammar)); + + $blueprint = clone $base; + $this->assertEquals([ + 'alter table "posts" add column "note" varchar(255) null', + ], $blueprint->toSql($connection, new PostgresGrammar)); + + $blueprint = clone $base; + $this->assertEquals([ + 'alter table "posts" add "note" nvarchar(255) null', + ], $blueprint->toSql($connection, new SqlServerGrammar)); + } } From cf439a011a2f866aa54f7b8c8258ad11d35abfa7 Mon Sep 17 00:00:00 2001 From: Mohamed Said Date: Mon, 12 Apr 2021 14:25:07 +0200 Subject: [PATCH 23/31] [8.x] Add a method to remove a resolved view engine (#36955) * add a method to remove a resolved view engine * clean --- src/Illuminate/View/Engines/EngineResolver.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Illuminate/View/Engines/EngineResolver.php b/src/Illuminate/View/Engines/EngineResolver.php index d0edb7367df6..6a5b80026342 100755 --- a/src/Illuminate/View/Engines/EngineResolver.php +++ b/src/Illuminate/View/Engines/EngineResolver.php @@ -57,4 +57,15 @@ public function resolve($engine) throw new InvalidArgumentException("Engine [{$engine}] not found."); } + + /** + * Remove a resolved engine. + * + * @param string $engine + * @return void + */ + public function forget($engine) + { + unset($this->resolved[$engine]); + } } From 245a7125076e52da7ce55b494c1c01f0f28df55d Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 12 Apr 2021 10:26:57 -0500 Subject: [PATCH 24/31] formatting --- src/Illuminate/Http/Client/PendingRequest.php | 66 +++++++++---------- src/Illuminate/Http/Client/Pool.php | 3 +- tests/Http/HttpClientTest.php | 6 +- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 56f29dec2e5e..f0790c5a54a3 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -26,7 +26,7 @@ class PendingRequest protected $factory; /** - * The client instance. + * The Guzzle client instance. * * @var \GuzzleHttp\Client */ @@ -595,6 +595,27 @@ public function delete($url, $data = []) ]); } + /** + * Send a pool of asynchronous requests concurrently. + * + * @param callable $callback + * @return array + */ + public function pool(callable $callback) + { + $results = []; + + $requests = tap(new Pool($this->factory), $callback)->getRequests(); + + foreach ($requests as $key => $item) { + $results[$key] = $item instanceof static ? $item->getPromise()->wait() : $item->wait(); + } + + ksort($results); + + return $results; + } + /** * Send the request to the given URL. * @@ -742,19 +763,6 @@ protected function populateResponse(Response $response) return $response; } - /** - * Set the client instance. - * - * @param \GuzzleHttp\Client $client - * @return $this - */ - public function setClient(Client $client) - { - $this->client = $client; - - return $this; - } - /** * Build the Guzzle client. * @@ -932,33 +940,25 @@ public function async(bool $async = true) } /** - * Send a pool of asynchronous requests concurrently. + * Retrieve the pending request promise. * - * @param callable $callback - * @return array + * @return \GuzzleHttp\Promise\PromiseInterface|null */ - public function pool(callable $callback) + public function getPromise() { - $results = []; - - $requests = tap(new Pool($this->factory), $callback)->getRequests(); - - foreach ($requests as $key => $item) { - $results[$key] = $item instanceof static ? $item->getPromise()->wait() : $item->wait(); - } - - ksort($results); - - return $results; + return $this->promise; } /** - * Retrieve the pending request promise. + * Set the client instance. * - * @return \GuzzleHttp\Promise\PromiseInterface|null + * @param \GuzzleHttp\Client $client + * @return $this */ - public function getPromise() + public function setClient(Client $client) { - return $this->promise; + $this->client = $client; + + return $this; } } diff --git a/src/Illuminate/Http/Client/Pool.php b/src/Illuminate/Http/Client/Pool.php index 8b04f4300155..15002b28e92e 100644 --- a/src/Illuminate/Http/Client/Pool.php +++ b/src/Illuminate/Http/Client/Pool.php @@ -44,7 +44,7 @@ public function __construct(Factory $factory = null) * @param string $key * @return \Illuminate\Http\Client\PendingRequest */ - public function add(string $key) + public function as(string $key) { return $this->pool[$key] = $this->asyncRequest(); } @@ -56,7 +56,6 @@ public function add(string $key) */ protected function asyncRequest() { - // the same client instance needs to be shared across all async requests return $this->factory->setClient($this->client)->async(); } diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 473a5b4e06c5..84709fa3ba66 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -892,9 +892,9 @@ public function testMultipleRequestsAreSentInThePoolWithKeys() $responses = $this->factory->pool(function (Pool $pool) { return [ - $pool->add('test200')->get('200.com'), - $pool->add('test400')->get('400.com'), - $pool->add('test500')->get('500.com'), + $pool->as('test200')->get('200.com'), + $pool->as('test400')->get('400.com'), + $pool->as('test500')->get('500.com'), ]; }); From 9a9f59fcc6e7b93465ce9848b52a473477dff64a Mon Sep 17 00:00:00 2001 From: donnysim Date: Tue, 13 Apr 2021 09:19:23 +0300 Subject: [PATCH 25/31] refactor: extract attribute getter for insert. --- src/Illuminate/Database/Eloquent/Model.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 245a103bce40..e8dea2327933 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -989,6 +989,16 @@ protected function getKeyForSaveQuery() return $this->original[$this->getKeyName()] ?? $this->getKey(); } + /** + * Get all of the current attributes on the model for insert. + * + * @return array + */ + protected function getAttributesForInsert() + { + return $this->getAttributes(); + } + /** * Perform a model insert operation. * @@ -1011,7 +1021,7 @@ protected function performInsert(Builder $query) // If the model has an incrementing key, we can use the "insertGetId" method on // the query builder, which will give us back the final inserted ID for this // table from the database. Not all tables have to be incrementing though. - $attributes = $this->getAttributes(); + $attributes = $this->getAttributesForInsert(); if ($this->getIncrementing()) { $this->insertAndSetId($query, $attributes); From 314bf875ba5d37c056ccea5148181fcb0517f596 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 13 Apr 2021 07:47:31 -0500 Subject: [PATCH 26/31] formatting' --- .../Database/Eloquent/Concerns/HasAttributes.php | 10 ++++++++++ src/Illuminate/Database/Eloquent/Model.php | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 459b14c73399..7d660905e393 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1322,6 +1322,16 @@ public function getAttributes() return $this->attributes; } + /** + * Get all of the current attributes on the model for an insert operation. + * + * @return array + */ + protected function getAttributesForInsert() + { + return $this->getAttributes(); + } + /** * Set the array of model attributes. No checking is done. * diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index e8dea2327933..512d59eae10e 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -989,16 +989,6 @@ protected function getKeyForSaveQuery() return $this->original[$this->getKeyName()] ?? $this->getKey(); } - /** - * Get all of the current attributes on the model for insert. - * - * @return array - */ - protected function getAttributesForInsert() - { - return $this->getAttributes(); - } - /** * Perform a model insert operation. * From b72479aa710d10ad1a88ad8f550fe87f5ee7cd78 Mon Sep 17 00:00:00 2001 From: Jonathan Reinink Date: Tue, 13 Apr 2021 08:54:24 -0400 Subject: [PATCH 27/31] Make pagination linkCollection() method public (#36959) --- src/Illuminate/Pagination/LengthAwarePaginator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Pagination/LengthAwarePaginator.php b/src/Illuminate/Pagination/LengthAwarePaginator.php index 3e5adad0a316..d1c6cc711fb5 100644 --- a/src/Illuminate/Pagination/LengthAwarePaginator.php +++ b/src/Illuminate/Pagination/LengthAwarePaginator.php @@ -99,7 +99,7 @@ public function render($view = null, $data = []) * * @return \Illuminate\Support\Collection */ - protected function linkCollection() + public function linkCollection() { return collect($this->elements())->flatMap(function ($item) { if (! is_array($item)) { From e1f36fa3819c0d4b155dadcf19926d73045b9784 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 13 Apr 2021 15:06:47 +0200 Subject: [PATCH 28/31] [6.x] Fix required_if boolean validation (#36967) * Fix required_if boolean validation * Update ValidatesAttributes.php Co-authored-by: Taylor Otwell --- .../Validation/Concerns/ValidatesAttributes.php | 13 ++++++++++++- tests/Validation/ValidationValidatorTest.php | 11 +++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 13fe1a648108..68b38aa8491c 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -1475,13 +1475,24 @@ protected function prepareValuesAndOther($parameters) $values = array_slice($parameters, 1); - if (is_bool($other)) { + if ($this->shouldConvertToBoolean($parameters[0]) || is_bool($other)) { $values = $this->convertValuesToBoolean($values); } return [$values, $other]; } + /** + * Check if parameter should be converted to boolean. + * + * @param string $parameter + * @return bool + */ + protected function shouldConvertToBoolean($parameter) + { + return in_array('boolean', Arr::get($this->rules, $parameter, [])); + } + /** * Convert the given values to boolean if they are string "true" / "false". * diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 4ac71213c984..2938f18e92b0 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -1090,6 +1090,17 @@ public function testRequiredIf() $v = new Validator($trans, ['first' => 'dayle', 'last' => ''], ['last' => 'RequiredIf:first,taylor,dayle']); $this->assertFalse($v->passes()); $this->assertSame('The last field is required when first is dayle.', $v->messages()->first('last')); + + $trans = $this->getIlluminateArrayTranslator(); + $trans->addLines(['validation.required_if' => 'The :attribute field is required when :other is :value.'], 'en'); + $v = new Validator($trans, ['foo' => 0], [ + 'foo' => 'required|boolean', + 'bar' => 'required_if:foo,true', + 'baz' => 'required_if:foo,false', + ]); + $this->assertTrue($v->fails()); + $this->assertCount(1, $v->messages()); + $this->assertSame('The baz field is required when foo is 0.', $v->messages()->first('baz')); } public function testRequiredUnless() From b24b32e8c518b70b7f56247168a8a33ec2f2de99 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 13 Apr 2021 08:14:08 -0500 Subject: [PATCH 29/31] formatting --- src/Illuminate/Database/Migrations/Migrator.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Migrations/Migrator.php b/src/Illuminate/Database/Migrations/Migrator.php index b51db2a142c5..35b4bb92e92a 100755 --- a/src/Illuminate/Database/Migrations/Migrator.php +++ b/src/Illuminate/Database/Migrations/Migrator.php @@ -187,6 +187,7 @@ protected function runUp($file, $batch, $pretend) // migration file name. Once we have the instances we can run the actual // command such as "up" or "down", or we can just simulate the action. $migration = $this->resolvePath($file); + $name = $this->getMigrationName($file); if ($pretend) { @@ -349,6 +350,7 @@ protected function runDown($file, $migration, $pretend) // instance of the migration. Once we get an instance we can either run a // pretend execution of the migration or we can run the real migration. $instance = $this->resolvePath($file); + $name = $this->getMigrationName($file); $this->note("Rolling back: {$name}"); @@ -413,6 +415,7 @@ protected function pretendToRun($migration, $method) $name = get_class($migration); $reflectionClass = new ReflectionClass($migration); + if ($reflectionClass->isAnonymous()) { $name = $this->getMigrationName($reflectionClass->getFileName()); } @@ -458,7 +461,7 @@ public function resolve($file) } /** - * Resolve a migration instance from migration path. + * Resolve a migration instance from a migration path. * * @param string $path * @return object @@ -466,6 +469,7 @@ public function resolve($file) protected function resolvePath(string $path) { $class = $this->getMigrationClass($this->getMigrationName($path)); + if (class_exists($class)) { return new $class; } @@ -474,7 +478,7 @@ protected function resolvePath(string $path) } /** - * Generate migration class name based on migration name. + * Generate a migration class name based on the migration file name. * * @param string $migrationName * @return string From 63655bf44ad7c3b2483c4a3c112831326f92e68c Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 13 Apr 2021 15:39:47 +0200 Subject: [PATCH 30/31] Revert "[6.x] Fix required_if boolean validation (#36967)" This reverts commit e1f36fa3819c0d4b155dadcf19926d73045b9784. --- .../Validation/Concerns/ValidatesAttributes.php | 13 +------------ tests/Validation/ValidationValidatorTest.php | 11 ----------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 68b38aa8491c..13fe1a648108 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -1475,24 +1475,13 @@ protected function prepareValuesAndOther($parameters) $values = array_slice($parameters, 1); - if ($this->shouldConvertToBoolean($parameters[0]) || is_bool($other)) { + if (is_bool($other)) { $values = $this->convertValuesToBoolean($values); } return [$values, $other]; } - /** - * Check if parameter should be converted to boolean. - * - * @param string $parameter - * @return bool - */ - protected function shouldConvertToBoolean($parameter) - { - return in_array('boolean', Arr::get($this->rules, $parameter, [])); - } - /** * Convert the given values to boolean if they are string "true" / "false". * diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 2938f18e92b0..4ac71213c984 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -1090,17 +1090,6 @@ public function testRequiredIf() $v = new Validator($trans, ['first' => 'dayle', 'last' => ''], ['last' => 'RequiredIf:first,taylor,dayle']); $this->assertFalse($v->passes()); $this->assertSame('The last field is required when first is dayle.', $v->messages()->first('last')); - - $trans = $this->getIlluminateArrayTranslator(); - $trans->addLines(['validation.required_if' => 'The :attribute field is required when :other is :value.'], 'en'); - $v = new Validator($trans, ['foo' => 0], [ - 'foo' => 'required|boolean', - 'bar' => 'required_if:foo,true', - 'baz' => 'required_if:foo,false', - ]); - $this->assertTrue($v->fails()); - $this->assertCount(1, $v->messages()); - $this->assertSame('The baz field is required when foo is 0.', $v->messages()->first('baz')); } public function testRequiredUnless() From cf4082973abc796ec285190f0603380021f6d26f Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 13 Apr 2021 08:49:49 -0500 Subject: [PATCH 31/31] wip --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index f7d1716456d0..b9a9eecbbca9 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -33,7 +33,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '8.36.2'; + const VERSION = '8.37.0'; /** * The base path for the Laravel installation.