From f812cf90326e5957584255593c86e85171cce1e9 Mon Sep 17 00:00:00 2001 From: Piotr Adamczyk Date: Sat, 13 Jan 2024 19:42:08 +0100 Subject: [PATCH 001/325] [10.x] feat: add base argument to Stringable->toInteger() (#49670) * feat: add base argument to Stringable->toInteger() * fix: StyleCi --- src/Illuminate/Support/Stringable.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index c89abb01e41c..144cbb8c5537 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -1281,11 +1281,12 @@ public function toString() /** * Get the underlying string value as an integer. * + * @param int $base * @return int */ - public function toInteger() + public function toInteger($base = 10) { - return intval($this->value); + return intval($this->value, $base); } /** From 6d2f90b66c6977fc1a19bedbf2a531cfedf28d15 Mon Sep 17 00:00:00 2001 From: Su Date: Sun, 14 Jan 2024 01:44:41 +0700 Subject: [PATCH 002/325] [10.x]: Remove unused class ShouldBeUnique when make a job (#49669) --- src/Illuminate/Foundation/Console/stubs/job.queued.stub | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Illuminate/Foundation/Console/stubs/job.queued.stub b/src/Illuminate/Foundation/Console/stubs/job.queued.stub index bc67adcf4790..9a7cec52a433 100644 --- a/src/Illuminate/Foundation/Console/stubs/job.queued.stub +++ b/src/Illuminate/Foundation/Console/stubs/job.queued.stub @@ -3,7 +3,6 @@ namespace {{ namespace }}; use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; From c7d5e0ee29b799ea66117be5c36f6b7efc1ca33d Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Sun, 14 Jan 2024 20:08:40 +0330 Subject: [PATCH 003/325] [10.x] Add tests for Eloquent methods (#49673) * Create EloquentModelLoadMaxTest.php * Create EloquentModelLoadMinTest.php * Create EloquentModelLoadSumTest.php --- .../Database/EloquentModelLoadMaxTest.php | 104 ++++++++++++++++++ .../Database/EloquentModelLoadMinTest.php | 104 ++++++++++++++++++ .../Database/EloquentModelLoadSumTest.php | 103 +++++++++++++++++ 3 files changed, 311 insertions(+) create mode 100644 tests/Integration/Database/EloquentModelLoadMaxTest.php create mode 100644 tests/Integration/Database/EloquentModelLoadMinTest.php create mode 100644 tests/Integration/Database/EloquentModelLoadSumTest.php diff --git a/tests/Integration/Database/EloquentModelLoadMaxTest.php b/tests/Integration/Database/EloquentModelLoadMaxTest.php new file mode 100644 index 000000000000..cf6a5d7bd8c7 --- /dev/null +++ b/tests/Integration/Database/EloquentModelLoadMaxTest.php @@ -0,0 +1,104 @@ +increments('id'); + }); + + Schema::create('related1s', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('base_model_id'); + $table->integer('number'); + }); + + Schema::create('related2s', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('base_model_id'); + $table->integer('number'); + }); + + BaseModel::create(); + + Related1::create(['base_model_id' => 1, 'number' => 10]); + Related1::create(['base_model_id' => 1, 'number' => 11]); + Related2::create(['base_model_id' => 1, 'number' => 12]); + Related2::create(['base_model_id' => 1, 'number' => 13]); + } + + public function testLoadMaxSingleRelation() + { + $model = BaseModel::first(); + + DB::enableQueryLog(); + + $model->loadMax('related1', 'number'); + + $this->assertCount(1, DB::getQueryLog()); + $this->assertEquals(11, $model->related1_max_number); + } + + public function testLoadMaxMultipleRelations() + { + $model = BaseModel::first(); + + DB::enableQueryLog(); + + $model->loadMax(['related1', 'related2'], 'number'); + + $this->assertCount(1, DB::getQueryLog()); + $this->assertEquals(11, $model->related1_max_number); + $this->assertEquals(13, $model->related2_max_number); + } +} + +class BaseModel extends Model +{ + public $timestamps = false; + + protected $guarded = []; + + public function related1() + { + return $this->hasMany(Related1::class); + } + + public function related2() + { + return $this->hasMany(Related2::class); + } +} + +class Related1 extends Model +{ + public $timestamps = false; + + protected $fillable = ['base_model_id', 'number']; + + public function parent() + { + return $this->belongsTo(BaseModel::class); + } +} + +class Related2 extends Model +{ + public $timestamps = false; + + protected $fillable = ['base_model_id', 'number']; + + public function parent() + { + return $this->belongsTo(BaseModel::class); + } +} diff --git a/tests/Integration/Database/EloquentModelLoadMinTest.php b/tests/Integration/Database/EloquentModelLoadMinTest.php new file mode 100644 index 000000000000..b63e2ec2dc47 --- /dev/null +++ b/tests/Integration/Database/EloquentModelLoadMinTest.php @@ -0,0 +1,104 @@ +increments('id'); + }); + + Schema::create('related1s', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('base_model_id'); + $table->integer('number'); + }); + + Schema::create('related2s', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('base_model_id'); + $table->integer('number'); + }); + + BaseModel::create(); + + Related1::create(['base_model_id' => 1, 'number' => 10]); + Related1::create(['base_model_id' => 1, 'number' => 11]); + Related2::create(['base_model_id' => 1, 'number' => 12]); + Related2::create(['base_model_id' => 1, 'number' => 13]); + } + + public function testLoadMinSingleRelation() + { + $model = BaseModel::first(); + + DB::enableQueryLog(); + + $model->loadMin('related1', 'number'); + + $this->assertCount(1, DB::getQueryLog()); + $this->assertEquals(10, $model->related1_min_number); + } + + public function testLoadMinMultipleRelations() + { + $model = BaseModel::first(); + + DB::enableQueryLog(); + + $model->loadMin(['related1', 'related2'], 'number'); + + $this->assertCount(1, DB::getQueryLog()); + $this->assertEquals(10, $model->related1_min_number); + $this->assertEquals(12, $model->related2_min_number); + } +} + +class BaseModel extends Model +{ + public $timestamps = false; + + protected $guarded = []; + + public function related1() + { + return $this->hasMany(Related1::class); + } + + public function related2() + { + return $this->hasMany(Related2::class); + } +} + +class Related1 extends Model +{ + public $timestamps = false; + + protected $fillable = ['base_model_id', 'number']; + + public function parent() + { + return $this->belongsTo(BaseModel::class); + } +} + +class Related2 extends Model +{ + public $timestamps = false; + + protected $fillable = ['base_model_id', 'number']; + + public function parent() + { + return $this->belongsTo(BaseModel::class); + } +} diff --git a/tests/Integration/Database/EloquentModelLoadSumTest.php b/tests/Integration/Database/EloquentModelLoadSumTest.php new file mode 100644 index 000000000000..0e4e5fa84b8d --- /dev/null +++ b/tests/Integration/Database/EloquentModelLoadSumTest.php @@ -0,0 +1,103 @@ +increments('id'); + }); + + Schema::create('related1s', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('base_model_id'); + $table->integer('number'); + }); + + Schema::create('related2s', function (Blueprint $table) { + $table->increments('id'); + $table->unsignedInteger('base_model_id'); + $table->integer('number'); + }); + + BaseModel::create(); + + Related1::create(['base_model_id' => 1, 'number' => 10]); + Related1::create(['base_model_id' => 1, 'number' => 11]); + Related2::create(['base_model_id' => 1, 'number' => 12]); + } + + public function testLoadSumSingleRelation() + { + $model = BaseModel::first(); + + DB::enableQueryLog(); + + $model->loadSum('related1', 'number'); + + $this->assertCount(1, DB::getQueryLog()); + $this->assertEquals(21, $model->related1_sum_number); + } + + public function testLoadSumMultipleRelations() + { + $model = BaseModel::first(); + + DB::enableQueryLog(); + + $model->loadSum(['related1', 'related2'], 'number'); + + $this->assertCount(1, DB::getQueryLog()); + $this->assertEquals(21, $model->related1_sum_number); + $this->assertEquals(12, $model->related2_sum_number); + } +} + +class BaseModel extends Model +{ + public $timestamps = false; + + protected $guarded = []; + + public function related1() + { + return $this->hasMany(Related1::class); + } + + public function related2() + { + return $this->hasMany(Related2::class); + } +} + +class Related1 extends Model +{ + public $timestamps = false; + + protected $fillable = ['base_model_id', 'number']; + + public function parent() + { + return $this->belongsTo(BaseModel::class); + } +} + +class Related2 extends Model +{ + public $timestamps = false; + + protected $fillable = ['base_model_id', 'number']; + + public function parent() + { + return $this->belongsTo(BaseModel::class); + } +} From fc0b6876ab29927a6d664d796b5c05dc70ca045e Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Mon, 15 Jan 2024 16:53:03 +0100 Subject: [PATCH 004/325] Implement draft workflow (#49683) --- .github/workflows/pull-requests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 18b32b3261a9..050ad42f7479 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -8,5 +8,7 @@ permissions: pull-requests: write jobs: + draft: + uses: laravel/.github/.github/workflows/pull-requests.yml@main uneditable: uses: laravel/.github/.github/workflows/pull-requests.yml@main From 024442ddb828c0db8beb422a931f7c6a35670046 Mon Sep 17 00:00:00 2001 From: AJ <60591772+devajmeireles@users.noreply.github.com> Date: Mon, 15 Jan 2024 12:56:04 -0300 Subject: [PATCH 005/325] code (#49681) --- src/Illuminate/Support/Number.php | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Support/Number.php b/src/Illuminate/Support/Number.php index b6f06178a1e4..7719055f6224 100644 --- a/src/Illuminate/Support/Number.php +++ b/src/Illuminate/Support/Number.php @@ -144,12 +144,12 @@ public static function fileSize(int|float $bytes, int $precision = 0, ?int $maxP } /** - * Convert the number to its human readable equivalent. + * Convert the number to its human-readable equivalent. * - * @param int $number + * @param int|float $number * @param int $precision * @param int|null $maxPrecision - * @return string + * @return bool|string */ public static function abbreviate(int|float $number, int $precision = 0, ?int $maxPrecision = null) { @@ -157,12 +157,13 @@ public static function abbreviate(int|float $number, int $precision = 0, ?int $m } /** - * Convert the number to its human readable equivalent. + * Convert the number to its human-readable equivalent. * - * @param int $number + * @param int|float $number * @param int $precision * @param int|null $maxPrecision - * @return string + * @param bool $abbreviate + * @return bool|string */ public static function forHumans(int|float $number, int $precision = 0, ?int $maxPrecision = null, bool $abbreviate = false) { @@ -182,13 +183,13 @@ public static function forHumans(int|float $number, int $precision = 0, ?int $ma } /** - * Convert the number to its human readable equivalent. + * Convert the number to its human-readable equivalent. * - * @param int $number + * @param int|float $number * @param int $precision * @param int|null $maxPrecision * @param array $units - * @return string + * @return string|false */ protected static function summarize(int|float $number, int $precision = 0, ?int $maxPrecision = null, array $units = []) { From c1e78c4ef1631a7c9c81ce22ac00d49b3bbce4dd Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Mon, 15 Jan 2024 17:01:20 +0100 Subject: [PATCH 006/325] Update pull-requests.yml --- .github/workflows/pull-requests.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 050ad42f7479..2aa858fb68e0 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -8,7 +8,5 @@ permissions: pull-requests: write jobs: - draft: - uses: laravel/.github/.github/workflows/pull-requests.yml@main - uneditable: + pull-requests: uses: laravel/.github/.github/workflows/pull-requests.yml@main From 11dd6449c3647f41e7c20c0e6dbc1b38b93b6992 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 16 Jan 2024 00:13:50 +0800 Subject: [PATCH 007/325] [10.x] Test Improvements (#49679) Signed-off-by: Mior Muhammad Zaki --- tests/Support/ConfigurationUrlParserTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Support/ConfigurationUrlParserTest.php b/tests/Support/ConfigurationUrlParserTest.php index 9035d08a45b6..22edc49810fe 100644 --- a/tests/Support/ConfigurationUrlParserTest.php +++ b/tests/Support/ConfigurationUrlParserTest.php @@ -135,7 +135,7 @@ public static function databaseUrls() ], ], 'query params from URL are used as extra params' => [ - 'url' => 'mysql://foo:bar@localhost/database?charset=UTF-8', + 'mysql://foo:bar@localhost/database?charset=UTF-8', [ 'driver' => 'mysql', 'database' => 'database', From 9fec940b5c5382136beb25954ecca060337bb535 Mon Sep 17 00:00:00 2001 From: Phil Bates Date: Mon, 15 Jan 2024 18:52:32 +0000 Subject: [PATCH 008/325] [10.x] Officially support floats in trans_choice (#49693) This already works fine with no further changes needed, so I'm not sure if there's a reason that floats were never officially supported. This is how it is currently, which you can see works perfectly fine: Given: // lang/en/foo.php return [ 'hours' => 'A total of :hours hour|A total of :hours hours', ]; Then: trans_choice('foo.hours', 1, ['hours' => 1]) === 'A total of 1 hour' trans_choice('foo.hours', 1.0, ['hours' => 1.0]) === 'A total of 1 hour' trans_choice('foo.hours', 1.1, ['hours' => 1.1]) === 'A total of 1.1 hours' trans_choice('foo.hours', 0.9, ['hours' => 0.9]) === 'A total of 0.9 hours' However, when running phpstan & larastan on a Laravel project that passes a float to trans_choice when wanting to display text similar to those examples ("A total of X hour[s]") it results in an error because the only documented allowed types are \Countable|int|array. Co-authored-by: Phil Bates --- src/Illuminate/Contracts/Translation/Translator.php | 2 +- src/Illuminate/Foundation/helpers.php | 2 +- .../Translation/PotentiallyTranslatedString.php | 2 +- src/Illuminate/Translation/Translator.php | 2 +- tests/Translation/TranslationTranslatorTest.php | 12 +++++++++++- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Contracts/Translation/Translator.php b/src/Illuminate/Contracts/Translation/Translator.php index 6eae4915d5a1..ded1a8b864f9 100644 --- a/src/Illuminate/Contracts/Translation/Translator.php +++ b/src/Illuminate/Contracts/Translation/Translator.php @@ -18,7 +18,7 @@ public function get($key, array $replace = [], $locale = null); * Get a translation according to an integer value. * * @param string $key - * @param \Countable|int|array $number + * @param \Countable|int|float|array $number * @param array $replace * @param string|null $locale * @return string diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index e4c15edca725..c698bbdfaa8e 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -929,7 +929,7 @@ function trans($key = null, $replace = [], $locale = null) * Translates the given message based on a count. * * @param string $key - * @param \Countable|int|array $number + * @param \Countable|int|float|array $number * @param array $replace * @param string|null $locale * @return string diff --git a/src/Illuminate/Translation/PotentiallyTranslatedString.php b/src/Illuminate/Translation/PotentiallyTranslatedString.php index f46db3522429..efcccca28331 100644 --- a/src/Illuminate/Translation/PotentiallyTranslatedString.php +++ b/src/Illuminate/Translation/PotentiallyTranslatedString.php @@ -57,7 +57,7 @@ public function translate($replace = [], $locale = null) /** * Translates the string based on a count. * - * @param \Countable|int|array $number + * @param \Countable|int|float|array $number * @param array $replace * @param string|null $locale * @return $this diff --git a/src/Illuminate/Translation/Translator.php b/src/Illuminate/Translation/Translator.php index f9f8b49cb11c..634a102d54c7 100755 --- a/src/Illuminate/Translation/Translator.php +++ b/src/Illuminate/Translation/Translator.php @@ -183,7 +183,7 @@ public function get($key, array $replace = [], $locale = null, $fallback = true) * Get a translation according to an integer value. * * @param string $key - * @param \Countable|int|array $number + * @param \Countable|int|float|array $number * @param array $replace * @param string|null $locale * @return string diff --git a/tests/Translation/TranslationTranslatorTest.php b/tests/Translation/TranslationTranslatorTest.php index 8601f1493ea7..07ec429aab77 100755 --- a/tests/Translation/TranslationTranslatorTest.php +++ b/tests/Translation/TranslationTranslatorTest.php @@ -123,7 +123,7 @@ public function testGetMethodProperlyLoadsAndRetrievesItemForGlobalNamespace() $this->assertSame('breeze bar', $t->get('foo.bar', ['foo' => 'bar'])); } - public function testChoiceMethodProperlyLoadsAndRetrievesItem() + public function testChoiceMethodProperlyLoadsAndRetrievesItemForAnInt() { $t = $this->getMockBuilder(Translator::class)->onlyMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock(); $t->expects($this->once())->method('get')->with($this->equalTo('foo'), $this->equalTo(['replace']), $this->equalTo('en'))->willReturn('line'); @@ -133,6 +133,16 @@ public function testChoiceMethodProperlyLoadsAndRetrievesItem() $t->choice('foo', 10, ['replace']); } + public function testChoiceMethodProperlyLoadsAndRetrievesItemForAFloat() + { + $t = $this->getMockBuilder(Translator::class)->onlyMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock(); + $t->expects($this->once())->method('get')->with($this->equalTo('foo'), $this->equalTo(['replace']), $this->equalTo('en'))->willReturn('line'); + $t->setSelector($selector = m::mock(MessageSelector::class)); + $selector->shouldReceive('choose')->once()->with('line', 1.2, 'en')->andReturn('choiced'); + + $t->choice('foo', 1.2, ['replace']); + } + public function testChoiceMethodProperlyCountsCollectionsAndLoadsAndRetrievesItem() { $t = $this->getMockBuilder(Translator::class)->onlyMethods(['get'])->setConstructorArgs([$this->getLoader(), 'en'])->getMock(); From 4d8c49fee5fefc8edb76d0abe60478b9451f17f9 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Mon, 15 Jan 2024 18:53:10 +0000 Subject: [PATCH 009/325] Update facade docblocks --- src/Illuminate/Support/Facades/Lang.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Lang.php b/src/Illuminate/Support/Facades/Lang.php index cdaad3d0fd16..a341b5fab640 100755 --- a/src/Illuminate/Support/Facades/Lang.php +++ b/src/Illuminate/Support/Facades/Lang.php @@ -6,7 +6,7 @@ * @method static bool hasForLocale(string $key, string|null $locale = null) * @method static bool has(string $key, string|null $locale = null, bool $fallback = true) * @method static string|array get(string $key, array $replace = [], string|null $locale = null, bool $fallback = true) - * @method static string choice(string $key, \Countable|int|array $number, array $replace = [], string|null $locale = null) + * @method static string choice(string $key, \Countable|int|float|array $number, array $replace = [], string|null $locale = null) * @method static void addLines(array $lines, string $locale, string $namespace = '*') * @method static void load(string $namespace, string $group, string $locale) * @method static \Illuminate\Translation\Translator handleMissingKeysUsing(callable|null $callback) From 8d4eaebc25d4349368d326f5fff80e15a0ffdd1d Mon Sep 17 00:00:00 2001 From: Michael Nabil <46572405+michaelnabil230@users.noreply.github.com> Date: Tue, 16 Jan 2024 02:22:35 +0200 Subject: [PATCH 010/325] Use a static function (#49696) --- src/Illuminate/Console/GeneratorCommand.php | 4 ++-- src/Illuminate/Database/Console/PruneCommand.php | 2 +- src/Illuminate/Foundation/Console/Kernel.php | 2 +- src/Illuminate/Foundation/Events/DiscoverEvents.php | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Console/GeneratorCommand.php b/src/Illuminate/Console/GeneratorCommand.php index f061dc67d384..efb349166b7f 100644 --- a/src/Illuminate/Console/GeneratorCommand.php +++ b/src/Illuminate/Console/GeneratorCommand.php @@ -249,7 +249,7 @@ protected function possibleModels() { $modelPath = is_dir(app_path('Models')) ? app_path('Models') : app_path(); - return collect((new Finder)->files()->depth(0)->in($modelPath)) + return collect(Finder::create()->files()->depth(0)->in($modelPath)) ->map(fn ($file) => $file->getBasename('.php')) ->sort() ->values() @@ -269,7 +269,7 @@ protected function possibleEvents() return []; } - return collect((new Finder)->files()->depth(0)->in($eventPath)) + return collect(Finder::create()->files()->depth(0)->in($eventPath)) ->map(fn ($file) => $file->getBasename('.php')) ->sort() ->values() diff --git a/src/Illuminate/Database/Console/PruneCommand.php b/src/Illuminate/Database/Console/PruneCommand.php index 2cb0a603ef79..23875d1187b9 100644 --- a/src/Illuminate/Database/Console/PruneCommand.php +++ b/src/Illuminate/Database/Console/PruneCommand.php @@ -126,7 +126,7 @@ protected function models() throw new InvalidArgumentException('The --models and --except options cannot be combined.'); } - return collect((new Finder)->in($this->getPath())->files()->name('*.php')) + return collect(Finder::create()->in($this->getPath())->files()->name('*.php')) ->map(function ($model) { $namespace = $this->laravel->getNamespace(); diff --git a/src/Illuminate/Foundation/Console/Kernel.php b/src/Illuminate/Foundation/Console/Kernel.php index 227f7caea513..7b19b42fa61b 100644 --- a/src/Illuminate/Foundation/Console/Kernel.php +++ b/src/Illuminate/Foundation/Console/Kernel.php @@ -340,7 +340,7 @@ protected function load($paths) $namespace = $this->app->getNamespace(); - foreach ((new Finder)->in($paths)->files() as $file) { + foreach (Finder::create()->in($paths)->files() as $file) { $command = $this->commandClassFromFile($file, $namespace); if (is_subclass_of($command, Command::class) && diff --git a/src/Illuminate/Foundation/Events/DiscoverEvents.php b/src/Illuminate/Foundation/Events/DiscoverEvents.php index b285759d2188..a4728c2ef3ed 100644 --- a/src/Illuminate/Foundation/Events/DiscoverEvents.php +++ b/src/Illuminate/Foundation/Events/DiscoverEvents.php @@ -29,7 +29,7 @@ class DiscoverEvents public static function within($listenerPath, $basePath) { $listeners = collect(static::getListenerEvents( - (new Finder)->files()->in($listenerPath), $basePath + Finder::create()->files()->in($listenerPath), $basePath )); $discoveredEvents = []; From 7cbdae560f66df62e02b8e0ced23459af105b88c Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 16 Jan 2024 15:41:20 +0100 Subject: [PATCH 011/325] Revert "[10.x] Improve numeric comparison for custom casts" (#49702) --- .../Eloquent/Concerns/HasAttributes.php | 2 +- ...DatabaseEloquentModelCustomCastingTest.php | 27 ------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 029d45637c63..991f06434a5b 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -2101,7 +2101,7 @@ public function originalIsEquivalent($key) } return is_numeric($attribute) && is_numeric($original) - && BigDecimal::of($attribute)->isEqualTo($original); + && strcmp((string) $attribute, (string) $original) === 0; } /** diff --git a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php index bfa3d533514b..8bbdb68c0271 100644 --- a/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php +++ b/tests/Integration/Database/DatabaseEloquentModelCustomCastingTest.php @@ -169,33 +169,6 @@ public function testDeviableCasts() $this->assertSame((new Decimal('320.988'))->getValue(), $model->price->getValue()); } - public function testDirtyOnCustomNumericCasts() - { - $model = new TestEloquentModelWithCustomCast; - $model->price = '123.00'; - $model->save(); - - $this->assertFalse($model->isDirty()); - - $model->price = '123.00'; - $this->assertFalse($model->isDirty('price')); - - $model->price = '123.0'; - $this->assertFalse($model->isDirty('price')); - - $model->price = '123'; - $this->assertFalse($model->isDirty('price')); - - $model->price = '00123.00'; - $this->assertFalse($model->isDirty('price')); - - $model->price = '123.4000'; - $this->assertTrue($model->isDirty('price')); - - $model->price = '123.0004'; - $this->assertTrue($model->isDirty('price')); - } - public function testSerializableCasts() { $model = new TestEloquentModelWithCustomCast; From 03eabd50cf00206bf043f49794f73158fe302b1c Mon Sep 17 00:00:00 2001 From: Kieran Date: Tue, 16 Jan 2024 15:08:28 +0000 Subject: [PATCH 012/325] [10.x] Add exit code to queue:clear, and queue:forget commands (#49707) * [10.x] Add exit code to queue:clear, and queue:forget commands * Update ClearCommand.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Queue/Console/ClearCommand.php | 2 ++ src/Illuminate/Queue/Console/ForgetFailedCommand.php | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Queue/Console/ClearCommand.php b/src/Illuminate/Queue/Console/ClearCommand.php index 6f3e8dc3bf9d..8f4187bcac77 100644 --- a/src/Illuminate/Queue/Console/ClearCommand.php +++ b/src/Illuminate/Queue/Console/ClearCommand.php @@ -57,6 +57,8 @@ public function handle() $this->components->info('Cleared '.$count.' '.Str::plural('job', $count).' from the ['.$queueName.'] queue'); } else { $this->components->error('Clearing queues is not supported on ['.(new ReflectionClass($queue))->getShortName().']'); + + return 1; } return 0; diff --git a/src/Illuminate/Queue/Console/ForgetFailedCommand.php b/src/Illuminate/Queue/Console/ForgetFailedCommand.php index 22d87d32b128..fce7803ccda7 100644 --- a/src/Illuminate/Queue/Console/ForgetFailedCommand.php +++ b/src/Illuminate/Queue/Console/ForgetFailedCommand.php @@ -25,7 +25,7 @@ class ForgetFailedCommand extends Command /** * Execute the console command. * - * @return void + * @return int|null */ public function handle() { @@ -33,6 +33,8 @@ public function handle() $this->components->info('Failed job deleted successfully.'); } else { $this->components->error('No failed job matches the given ID.'); + + return 1; } } } From 94d0fd7b74c47d1cb51aa09dad125a415412e445 Mon Sep 17 00:00:00 2001 From: Jan Stolle Date: Tue, 16 Jan 2024 16:11:13 +0100 Subject: [PATCH 013/325] Allow StreamInterface as raw HTTP Client body (#49705) * Allow StreamInterface as raw HTTP Client body Since the body payload is passed through to Guzzle, which in turn can handle Streams as well, this extends the versatility of the HTTP Client * Update PendingRequest.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Http/Client/PendingRequest.php | 4 ++-- tests/Http/HttpClientTest.php | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 78e361feffad..28defb4d9607 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -78,7 +78,7 @@ class PendingRequest /** * The raw body for the request. * - * @var string + * @var \Psr\Http\Message\StreamInterface|string */ protected $pendingBody; @@ -259,7 +259,7 @@ public function baseUrl(string $url) /** * Attach a raw body to the request. * - * @param string $content + * @param \Psr\Http\Message\StreamInterface|string $content * @param string $contentType * @return $this */ diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index df4f7165373c..6b790aa06534 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -6,6 +6,7 @@ use GuzzleHttp\Middleware; use GuzzleHttp\Promise\PromiseInterface; use GuzzleHttp\Psr7\Response as Psr7Response; +use GuzzleHttp\Psr7\Utils; use GuzzleHttp\TransferStats; use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Support\Arrayable; @@ -357,6 +358,26 @@ public function testSendRequestBodyWithManyAmpersands() $this->factory->withBody($body, 'text/plain')->send('post', 'http://foo.com/api'); } + public function testSendStreamRequestBody() + { + $string = 'Look at me, i am a stream!!'; + $resource = fopen('php://temp', 'w'); + fwrite($resource, $string); + rewind($resource); + $body = Utils::streamFor($resource); + + $fakeRequest = function (Request $request) use ($string) { + self::assertSame($string, $request->body()); + self::assertContains('text/plain', $request->header('Content-Type')); + + return ['my' => 'response']; + }; + + $this->factory->fake($fakeRequest); + + $this->factory->withBody($body, 'text/plain')->send('post', 'http://foo.com/api'); + } + public function testUrlsCanBeStubbedByPath() { $this->factory->fake([ From d056aabc2caffcdbf21927d464ecc1a6d145e401 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Tue, 16 Jan 2024 15:11:48 +0000 Subject: [PATCH 014/325] Update facade docblocks --- src/Illuminate/Support/Facades/Http.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index 6a445e078055..4c0cbd915eda 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -27,7 +27,7 @@ * @method static void flushMacros() * @method static mixed macroCall(string $method, array $parameters) * @method static \Illuminate\Http\Client\PendingRequest baseUrl(string $url) - * @method static \Illuminate\Http\Client\PendingRequest withBody(string $content, string $contentType = 'application/json') + * @method static \Illuminate\Http\Client\PendingRequest withBody(\Psr\Http\Message\StreamInterface|string $content, string $contentType = 'application/json') * @method static \Illuminate\Http\Client\PendingRequest asJson() * @method static \Illuminate\Http\Client\PendingRequest asForm() * @method static \Illuminate\Http\Client\PendingRequest attach(string|array $name, string|resource $contents = '', string|null $filename = null, array $headers = []) From da31969bd35e6ee0bbcd9e876f88952dc754b012 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 16 Jan 2024 09:23:58 -0600 Subject: [PATCH 015/325] version --- 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 930c406f7ebf..a352be0fc7bb 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.40.0'; + const VERSION = '10.41.0'; /** * The base path for the Laravel installation. From 158767fb835a63540845d266e58d821726fc0baa Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 16 Jan 2024 17:11:31 +0000 Subject: [PATCH 016/325] Update CHANGELOG --- CHANGELOG.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ae112019c8..1c8689860c49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,31 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.40.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.41.0...10.x) + +## [v10.41.0](https://github.com/laravel/framework/compare/v10.40.0...v10.41.0) - 2024-01-16 + +* [10.x] Add a `threshold` parameter to the `Number::spell` helper by [@caendesilva](https://github.com/caendesilva) in https://github.com/laravel/framework/pull/49610 +* Revert "[10.x] Make ComponentAttributeBag Arrayable" by [@luanfreitasdev](https://github.com/luanfreitasdev) in https://github.com/laravel/framework/pull/49623 +* [10.x] Fix return value and docblock by [@dwightwatson](https://github.com/dwightwatson) in https://github.com/laravel/framework/pull/49627 +* [10.x] Add an option to specify the default path to the models directory for `php artisan model:prune` by [@dbhynds](https://github.com/dbhynds) in https://github.com/laravel/framework/pull/49617 +* [10.x] Allow job chains to be conditionally dispatched by [@fjarrett](https://github.com/fjarrett) in https://github.com/laravel/framework/pull/49624 +* [10.x] Add test for existing empty test by [@lioneaglesolutions](https://github.com/lioneaglesolutions) in https://github.com/laravel/framework/pull/49632 +* [10.x] Add additional context to Mailable assertion messages by [@lioneaglesolutions](https://github.com/lioneaglesolutions) in https://github.com/laravel/framework/pull/49631 +* [10.x] Allow job batches to be conditionally dispatched by [@fjarrett](https://github.com/fjarrett) in https://github.com/laravel/framework/pull/49639 +* [10.x] Revert parameter name change by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/49659 +* [10.x] Printing Name of The Method that Calls `ensureIntlExtensionIsInstalled` in `Number` class. by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/49660 +* [10.x] Update pagination tailwind.blade.php by [@anasmorahhib](https://github.com/anasmorahhib) in https://github.com/laravel/framework/pull/49665 +* [10.x] feat: add base argument to Stringable->toInteger() by [@adamczykpiotr](https://github.com/adamczykpiotr) in https://github.com/laravel/framework/pull/49670 +* [10.x]: Remove unused class ShouldBeUnique when make a job by [@Kenini1805](https://github.com/Kenini1805) in https://github.com/laravel/framework/pull/49669 +* [10.x] Add tests for Eloquent methods by [@milwad-dev](https://github.com/milwad-dev) in https://github.com/laravel/framework/pull/49673 +* Implement draft workflow by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/49683 +* [10.x] Fixing Types, Word and Returns of `Number`class. by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/49681 +* [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49679 +* [10.x] Officially support floats in trans_choice and Translator::choice by [@philbates35](https://github.com/philbates35) in https://github.com/laravel/framework/pull/49693 +* [10.x] Use static function by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/49696 +* [10.x] Revert "[10.x] Improve numeric comparison for custom casts" by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/49702 +* [10.x] Add exit code to queue:clear, and queue:forget commands by [@bytestream](https://github.com/bytestream) in https://github.com/laravel/framework/pull/49707 +* [10.x] Allow StreamInterface as raw HTTP Client body by [@janolivermr](https://github.com/janolivermr) in https://github.com/laravel/framework/pull/49705 ## [v10.40.0](https://github.com/laravel/framework/compare/v10.39.0...v10.40.0) - 2024-01-09 From 72e9965635b736bee440dbcc565bceac9b255c5b Mon Sep 17 00:00:00 2001 From: Simon Hamp Date: Wed, 17 Jan 2024 15:07:27 +0000 Subject: [PATCH 017/325] Switch to hash_equals (#49721) --- src/Illuminate/Filesystem/Filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Filesystem/Filesystem.php b/src/Illuminate/Filesystem/Filesystem.php index 23fc17eeb03c..c9485337c299 100644 --- a/src/Illuminate/Filesystem/Filesystem.php +++ b/src/Illuminate/Filesystem/Filesystem.php @@ -546,7 +546,7 @@ public function hasSameHash($firstFile, $secondFile) { $hash = @md5_file($firstFile); - return $hash && $hash === @md5_file($secondFile); + return $hash && hash_equals($hash, (string) @md5_file($secondFile)); } /** From d710a0195f3d4cd5cb80290a94475ea0299a031f Mon Sep 17 00:00:00 2001 From: Daniel Bakan Date: Wed, 17 Jan 2024 22:16:48 +0100 Subject: [PATCH 018/325] fix Rule::unless for callable (#49726) --- src/Illuminate/Validation/Rule.php | 2 +- tests/Validation/ValidationRuleParserTest.php | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Rule.php b/src/Illuminate/Validation/Rule.php index 1f8286cc375f..df28c8938c60 100644 --- a/src/Illuminate/Validation/Rule.php +++ b/src/Illuminate/Validation/Rule.php @@ -56,7 +56,7 @@ public static function when($condition, $rules, $defaultRules = []) */ public static function unless($condition, $rules, $defaultRules = []) { - return new ConditionalRules(! $condition, $rules, $defaultRules); + return new ConditionalRules($condition, $defaultRules, $rules); } /** diff --git a/tests/Validation/ValidationRuleParserTest.php b/tests/Validation/ValidationRuleParserTest.php index 2ead8eecbb9d..367d8b59c24f 100644 --- a/tests/Validation/ValidationRuleParserTest.php +++ b/tests/Validation/ValidationRuleParserTest.php @@ -28,6 +28,10 @@ public function testConditionalRulesAreProperlyExpandedAndFiltered() 'zip' => ['required', Rule::when($isAdmin, function (Fluent $input) { return ['min:2']; })], + 'when_cb_true' => Rule::when(fn () => true, ['required'], ['nullable']), + 'when_cb_false' => Rule::when(fn () => false, ['required'], ['nullable']), + 'unless_cb_true' => Rule::unless(fn () => true, ['required'], ['nullable']), + 'unless_cb_false' => Rule::unless(fn () => false, ['required'], ['nullable']), ]); $this->assertEquals([ @@ -39,6 +43,10 @@ public function testConditionalRulesAreProperlyExpandedAndFiltered() 'city' => ['required', 'min:2'], 'state' => ['required', 'min:2'], 'zip' => ['required', 'min:2'], + 'when_cb_true' => ['required'], + 'when_cb_false' => ['nullable'], + 'unless_cb_true' => ['nullable'], + 'unless_cb_false' => ['required'], ], $rules); } From c62d15ba607b423a65da217f4201c1f4840ba1bf Mon Sep 17 00:00:00 2001 From: Daniel Mason Date: Wed, 17 Jan 2024 21:20:57 +0000 Subject: [PATCH 019/325] [10.x] Adds JobQueueing event (#49722) * Adds JobQueueing event * style fixes * fix broken tests * fix broken tests --- src/Illuminate/Queue/Events/JobQueueing.php | 58 +++++++++++++++++++ src/Illuminate/Queue/Queue.php | 19 ++++++ tests/Integration/Queue/RedisQueueTest.php | 27 +++++---- tests/Queue/QueueBeanstalkdQueueTest.php | 4 +- .../QueueDatabaseQueueIntegrationTest.php | 20 +++++-- tests/Queue/QueueDatabaseQueueUnitTest.php | 4 +- tests/Queue/QueueRedisQueueTest.php | 10 ++-- tests/Queue/QueueSqsQueueTest.php | 6 +- 8 files changed, 120 insertions(+), 28 deletions(-) create mode 100644 src/Illuminate/Queue/Events/JobQueueing.php diff --git a/src/Illuminate/Queue/Events/JobQueueing.php b/src/Illuminate/Queue/Events/JobQueueing.php new file mode 100644 index 000000000000..ebb0769681b4 --- /dev/null +++ b/src/Illuminate/Queue/Events/JobQueueing.php @@ -0,0 +1,58 @@ +connectionName = $connectionName; + $this->job = $job; + $this->payload = $payload; + } + + /** + * Get the decoded job payload. + * + * @return array + */ + public function payload() + { + if ($this->payload === null) { + throw new RuntimeException('The job payload was not provided when the event was dispatched.'); + } + + return json_decode($this->payload, true, flags: JSON_THROW_ON_ERROR); + } +} diff --git a/src/Illuminate/Queue/Queue.php b/src/Illuminate/Queue/Queue.php index 1aa09ee30bdb..09eb24526311 100755 --- a/src/Illuminate/Queue/Queue.php +++ b/src/Illuminate/Queue/Queue.php @@ -9,6 +9,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueueAfterCommit; use Illuminate\Queue\Events\JobQueued; +use Illuminate\Queue\Events\JobQueueing; use Illuminate\Support\Arr; use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Str; @@ -327,6 +328,8 @@ protected function enqueueUsing($job, $payload, $queue, $delay, $callback) $this->container->bound('db.transactions')) { return $this->container->make('db.transactions')->addCallback( function () use ($payload, $queue, $delay, $callback, $job) { + $this->raiseJobQueueingEvent($job, $payload); + return tap($callback($payload, $queue, $delay), function ($jobId) use ($job, $payload) { $this->raiseJobQueuedEvent($jobId, $job, $payload); }); @@ -334,6 +337,8 @@ function () use ($payload, $queue, $delay, $callback, $job) { ); } + $this->raiseJobQueueingEvent($job, $payload); + return tap($callback($payload, $queue, $delay), function ($jobId) use ($job, $payload) { $this->raiseJobQueuedEvent($jobId, $job, $payload); }); @@ -362,6 +367,20 @@ protected function shouldDispatchAfterCommit($job) return false; } + /** + * Raise the job queueing event. + * + * @param \Closure|string|object $job + * @param string $payload + * @return void + */ + protected function raiseJobQueueingEvent($job, $payload) + { + if ($this->container->bound('events')) { + $this->container['events']->dispatch(new JobQueueing($this->connectionName, $job, $payload)); + } + } + /** * Raise the job queued event. * diff --git a/tests/Integration/Queue/RedisQueueTest.php b/tests/Integration/Queue/RedisQueueTest.php index 74f1dffcec4a..0905ada8d4c3 100644 --- a/tests/Integration/Queue/RedisQueueTest.php +++ b/tests/Integration/Queue/RedisQueueTest.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; use Illuminate\Queue\Events\JobQueued; +use Illuminate\Queue\Events\JobQueueing; use Illuminate\Queue\Jobs\RedisJob; use Illuminate\Queue\RedisQueue; use Illuminate\Support\InteractsWithTime; @@ -200,7 +201,7 @@ public function testPopPopsDelayedJobOffOfRedisWhenExpireNull($driver) $job = new RedisQueueIntegrationTestJob(10); $this->queue->later(-10, $job); - $this->container->shouldHaveReceived('bound')->with('events')->once(); + $this->container->shouldHaveReceived('bound')->with('events')->twice(); // Pop and check it is popped correctly $before = $this->currentTime(); @@ -278,7 +279,7 @@ public function testNotExpireJobsWhenExpireNull($driver) // Make an expired reserved job $failed = new RedisQueueIntegrationTestJob(-20); $this->queue->push($failed); - $this->container->shouldHaveReceived('bound')->with('events')->once(); + $this->container->shouldHaveReceived('bound')->with('events')->twice(); $beforeFailPop = $this->currentTime(); $this->queue->pop(); @@ -287,7 +288,7 @@ public function testNotExpireJobsWhenExpireNull($driver) // Push an item into queue $job = new RedisQueueIntegrationTestJob(10); $this->queue->push($job); - $this->container->shouldHaveReceived('bound')->with('events')->times(2); + $this->container->shouldHaveReceived('bound')->with('events')->times(4); // Pop and check it is popped correctly $before = $this->currentTime(); @@ -326,7 +327,7 @@ public function testExpireJobsWhenExpireSet($driver) // Push an item into queue $job = new RedisQueueIntegrationTestJob(10); $this->queue->push($job); - $this->container->shouldHaveReceived('bound')->with('events')->once(); + $this->container->shouldHaveReceived('bound')->with('events')->twice(); // Pop and check it is popped correctly $before = $this->currentTime(); @@ -466,19 +467,24 @@ public function testSize($driver) * @param string $driver */ #[DataProvider('redisDriverProvider')] - public function testPushJobQueuedEvent($driver) + public function testPushJobQueueingAndJobQueuedEvents($driver) { $events = m::mock(Dispatcher::class); + $events->shouldReceive('dispatch')->withArgs(function (JobQueueing $jobQueuing) { + $this->assertInstanceOf(RedisQueueIntegrationTestJob::class, $jobQueuing->job); + + return true; + })->andReturnNull()->once(); $events->shouldReceive('dispatch')->withArgs(function (JobQueued $jobQueued) { $this->assertInstanceOf(RedisQueueIntegrationTestJob::class, $jobQueued->job); - $this->assertIsString(RedisQueueIntegrationTestJob::class, $jobQueued->id); + $this->assertIsString($jobQueued->id); return true; })->andReturnNull()->once(); $container = m::mock(Container::class); - $container->shouldReceive('bound')->with('events')->andReturn(true)->once(); - $container->shouldReceive('offsetGet')->with('events')->andReturn($events)->once(); + $container->shouldReceive('bound')->with('events')->andReturn(true)->twice(); + $container->shouldReceive('offsetGet')->with('events')->andReturn($events)->twice(); $queue = new RedisQueue($this->redis[$driver]); $queue->setContainer($container); @@ -493,11 +499,12 @@ public function testPushJobQueuedEvent($driver) public function testBulkJobQueuedEvent($driver) { $events = m::mock(Dispatcher::class); + $events->shouldReceive('dispatch')->with(m::type(JobQueueing::class))->andReturnNull()->times(3); $events->shouldReceive('dispatch')->with(m::type(JobQueued::class))->andReturnNull()->times(3); $container = m::mock(Container::class); - $container->shouldReceive('bound')->with('events')->andReturn(true)->times(3); - $container->shouldReceive('offsetGet')->with('events')->andReturn($events)->times(3); + $container->shouldReceive('bound')->with('events')->andReturn(true)->times(6); + $container->shouldReceive('offsetGet')->with('events')->andReturn($events)->times(6); $queue = new RedisQueue($this->redis[$driver]); $queue->setContainer($container); diff --git a/tests/Queue/QueueBeanstalkdQueueTest.php b/tests/Queue/QueueBeanstalkdQueueTest.php index ed4e6f904e20..beab4bfa98df 100755 --- a/tests/Queue/QueueBeanstalkdQueueTest.php +++ b/tests/Queue/QueueBeanstalkdQueueTest.php @@ -45,7 +45,7 @@ public function testPushProperlyPushesJobOntoBeanstalkd() $this->queue->push('foo', ['data'], 'stack'); $this->queue->push('foo', ['data']); - $this->container->shouldHaveReceived('bound')->with('events')->times(2); + $this->container->shouldHaveReceived('bound')->with('events')->times(4); Str::createUuidsNormally(); } @@ -67,7 +67,7 @@ public function testDelayedPushProperlyPushesJobOntoBeanstalkd() $this->queue->later(5, 'foo', ['data'], 'stack'); $this->queue->later(5, 'foo', ['data']); - $this->container->shouldHaveReceived('bound')->with('events')->times(2); + $this->container->shouldHaveReceived('bound')->with('events')->times(4); Str::createUuidsNormally(); } diff --git a/tests/Queue/QueueDatabaseQueueIntegrationTest.php b/tests/Queue/QueueDatabaseQueueIntegrationTest.php index 259fe46adcbe..4c4f7c91c5c1 100644 --- a/tests/Queue/QueueDatabaseQueueIntegrationTest.php +++ b/tests/Queue/QueueDatabaseQueueIntegrationTest.php @@ -9,6 +9,7 @@ use Illuminate\Events\Dispatcher; use Illuminate\Queue\DatabaseQueue; use Illuminate\Queue\Events\JobQueued; +use Illuminate\Queue\Events\JobQueueing; use Illuminate\Support\Carbon; use Illuminate\Support\Str; use PHPUnit\Framework\TestCase; @@ -247,21 +248,28 @@ public function testThatReservedJobsAreNotPopped() $this->assertNull($popped_job); } - public function testJobPayloadIsAvailableOnEvent() + public function testJobPayloadIsAvailableOnEvents() { - $event = null; + $jobQueueingEvent = null; + $jobQueuedEvent = null; Str::createUuidsUsingSequence([ 'expected-job-uuid', ]); - $this->container['events']->listen(function (JobQueued $e) use (&$event) { - $event = $e; + $this->container['events']->listen(function (JobQueueing $e) use (&$jobQueueingEvent) { + $jobQueueingEvent = $e; + }); + $this->container['events']->listen(function (JobQueued $e) use (&$jobQueuedEvent) { + $jobQueuedEvent = $e; }); $this->queue->push('MyJob', [ 'laravel' => 'Framework', ]); - $this->assertIsArray($event->payload()); - $this->assertSame('expected-job-uuid', $event->payload()['uuid']); + $this->assertIsArray($jobQueueingEvent->payload()); + $this->assertSame('expected-job-uuid', $jobQueueingEvent->payload()['uuid']); + + $this->assertIsArray($jobQueuedEvent->payload()); + $this->assertSame('expected-job-uuid', $jobQueuedEvent->payload()['uuid']); } } diff --git a/tests/Queue/QueueDatabaseQueueUnitTest.php b/tests/Queue/QueueDatabaseQueueUnitTest.php index 17087db4e592..53c65720551c 100644 --- a/tests/Queue/QueueDatabaseQueueUnitTest.php +++ b/tests/Queue/QueueDatabaseQueueUnitTest.php @@ -46,7 +46,7 @@ public function testPushProperlyPushesJobOntoDatabase($uuid, $job, $displayNameS $queue->push($job, ['data']); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); Str::createUuidsNormally(); } @@ -87,7 +87,7 @@ public function testDelayedPushProperlyPushesJobOntoDatabase() $queue->later(10, 'foo', ['data']); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); Str::createUuidsNormally(); } diff --git a/tests/Queue/QueueRedisQueueTest.php b/tests/Queue/QueueRedisQueueTest.php index 442676de71ce..007f743653d8 100644 --- a/tests/Queue/QueueRedisQueueTest.php +++ b/tests/Queue/QueueRedisQueueTest.php @@ -35,7 +35,7 @@ public function testPushProperlyPushesJobOntoRedis() $id = $queue->push('foo', ['data']); $this->assertSame('foo', $id); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); Str::createUuidsNormally(); } @@ -60,7 +60,7 @@ public function testPushProperlyPushesJobOntoRedisWithCustomPayloadHook() $id = $queue->push('foo', ['data']); $this->assertSame('foo', $id); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); Queue::createPayloadUsing(null); @@ -91,7 +91,7 @@ public function testPushProperlyPushesJobOntoRedisWithTwoCustomPayloadHook() $id = $queue->push('foo', ['data']); $this->assertSame('foo', $id); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); Queue::createPayloadUsing(null); @@ -120,7 +120,7 @@ public function testDelayedPushProperlyPushesJobOntoRedis() $id = $queue->later(1, 'foo', ['data']); $this->assertSame('foo', $id); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); Str::createUuidsNormally(); } @@ -147,7 +147,7 @@ public function testDelayedPushWithDateTimeProperlyPushesJobOntoRedis() ); $queue->later($date, 'foo', ['data']); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); Str::createUuidsNormally(); } diff --git a/tests/Queue/QueueSqsQueueTest.php b/tests/Queue/QueueSqsQueueTest.php index 3886a3f83854..021e66484b68 100755 --- a/tests/Queue/QueueSqsQueueTest.php +++ b/tests/Queue/QueueSqsQueueTest.php @@ -116,7 +116,7 @@ public function testDelayedPushWithDateTimeProperlyPushesJobOntoSqs() $this->sqs->shouldReceive('sendMessage')->once()->with(['QueueUrl' => $this->queueUrl, 'MessageBody' => $this->mockedPayload, 'DelaySeconds' => 5])->andReturn($this->mockedSendMessageResponseModel); $id = $queue->later($now->addSeconds(5), $this->mockedJob, $this->mockedData, $this->queueName); $this->assertEquals($this->mockedMessageId, $id); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); } public function testDelayedPushProperlyPushesJobOntoSqs() @@ -129,7 +129,7 @@ public function testDelayedPushProperlyPushesJobOntoSqs() $this->sqs->shouldReceive('sendMessage')->once()->with(['QueueUrl' => $this->queueUrl, 'MessageBody' => $this->mockedPayload, 'DelaySeconds' => $this->mockedDelay])->andReturn($this->mockedSendMessageResponseModel); $id = $queue->later($this->mockedDelay, $this->mockedJob, $this->mockedData, $this->queueName); $this->assertEquals($this->mockedMessageId, $id); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); } public function testPushProperlyPushesJobOntoSqs() @@ -141,7 +141,7 @@ public function testPushProperlyPushesJobOntoSqs() $this->sqs->shouldReceive('sendMessage')->once()->with(['QueueUrl' => $this->queueUrl, 'MessageBody' => $this->mockedPayload])->andReturn($this->mockedSendMessageResponseModel); $id = $queue->push($this->mockedJob, $this->mockedData, $this->queueName); $this->assertEquals($this->mockedMessageId, $id); - $container->shouldHaveReceived('bound')->with('events')->once(); + $container->shouldHaveReceived('bound')->with('events')->twice(); } public function testSizeProperlyReadsSqsQueueSize() From 3acc1d04423d3925a9592b5bae5158b9f9d58905 Mon Sep 17 00:00:00 2001 From: Roj Vroemen Date: Thu, 18 Jan 2024 17:43:59 +0100 Subject: [PATCH 020/325] [10.x] Fix decoding issue in MailLogTransport (#49727) * Handle multipart messages and only decode content * formatting --------- Co-authored-by: Taylor Otwell --- .../Mail/Transport/LogTransport.php | 40 +++++++++++++++++-- tests/Mail/MailLogTransportTest.php | 27 +++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Mail/Transport/LogTransport.php b/src/Illuminate/Mail/Transport/LogTransport.php index 291251200a48..682169e3eaa7 100644 --- a/src/Illuminate/Mail/Transport/LogTransport.php +++ b/src/Illuminate/Mail/Transport/LogTransport.php @@ -2,6 +2,7 @@ namespace Illuminate\Mail\Transport; +use Illuminate\Support\Str; use Psr\Log\LoggerInterface; use Symfony\Component\Mailer\Envelope; use Symfony\Component\Mailer\SentMessage; @@ -33,17 +34,48 @@ public function __construct(LoggerInterface $logger) */ public function send(RawMessage $message, Envelope $envelope = null): ?SentMessage { - $string = $message->toString(); + $string = Str::of($message->toString()); - if (str_contains($string, 'Content-Transfer-Encoding: quoted-printable')) { - $string = quoted_printable_decode($string); + if ($string->contains('Content-Type: multipart/')) { + $boundary = $string + ->after('boundary=') + ->before("\r\n") + ->prepend('--') + ->append("\r\n"); + + $string = $string + ->explode($boundary) + ->map($this->decodeQuotedPrintableContent(...)) + ->implode($boundary); + } elseif ($string->contains('Content-Transfer-Encoding: quoted-printable')) { + $string = $this->decodeQuotedPrintableContent($string); } - $this->logger->debug($string); + $this->logger->debug((string) $string); return new SentMessage($message, $envelope ?? Envelope::create($message)); } + /** + * Decode the given quoted printable content. + * + * @param string $part + * @return string + */ + protected function decodeQuotedPrintableContent(string $part) + { + if (! str_contains($part, 'Content-Transfer-Encoding: quoted-printable')) { + return $part; + } + + [$headers, $content] = explode("\r\n\r\n", $part, 2); + + return implode("\r\n\r\n", [ + $headers, + quoted_printable_decode($content), + ]); + } + /** * Get the logger for the LogTransport instance. * diff --git a/tests/Mail/MailLogTransportTest.php b/tests/Mail/MailLogTransportTest.php index 3bb0c69a44df..33367d52610c 100644 --- a/tests/Mail/MailLogTransportTest.php +++ b/tests/Mail/MailLogTransportTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Mail; +use Illuminate\Mail\Attachment; use Illuminate\Mail\Message; use Illuminate\Mail\Transport\LogTransport; use Monolog\Handler\StreamHandler; @@ -59,6 +60,32 @@ public function testItDecodesTheMessageBeforeLogging() $this->assertStringContainsString('https://example.com/reset-password=5e113c71a4c210aff04b3fa66f1b1299', $actualLoggedValue); } + public function testItOnlyDecodesQuotedPrintablePartsOfTheMessageBeforeLogging() + { + $message = (new Message(new Email)) + ->from('noreply@example.com', 'no-reply') + ->to('taylor@example.com', 'Taylor') + ->html(<<<'BODY' + Hi, + + Click here to reset your password. + + All the best, + + Burt & Irving + BODY) + ->text('A text part') + ->attach(Attachment::fromData(fn () => 'My attachment', 'attachment.txt')); + + $actualLoggedValue = $this->getLoggedEmailMessage($message); + + $this->assertStringContainsString('href=', $actualLoggedValue); + $this->assertStringContainsString('Burt & Irving', $actualLoggedValue); + $this->assertStringContainsString('https://example.com/reset-password=5e113c71a4c210aff04b3fa66f1b1299', $actualLoggedValue); + $this->assertStringContainsString('name=attachment.txt', $actualLoggedValue); + $this->assertStringContainsString('filename=attachment.txt', $actualLoggedValue); + } + public function testGetLogTransportWithPsrLogger() { $this->app['config']->set('mail.driver', 'log'); From 443ec4438c48923c9caa9c2b409a12b84a10033f Mon Sep 17 00:00:00 2001 From: Jeremy Angele <131715596+angelej@users.noreply.github.com> Date: Fri, 19 Jan 2024 14:35:56 +0100 Subject: [PATCH 021/325] Implement "max" validation rule for passwords (#49739) * Implement "max" validation rule for passwords * Update Password.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Validation/Rules/Password.php | 26 ++++++++++++++++++- .../Validation/ValidationPasswordRuleTest.php | 9 +++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Rules/Password.php b/src/Illuminate/Validation/Rules/Password.php index 93e06dc5d57a..588f357334cc 100644 --- a/src/Illuminate/Validation/Rules/Password.php +++ b/src/Illuminate/Validation/Rules/Password.php @@ -37,6 +37,13 @@ class Password implements Rule, DataAwareRule, ValidatorAwareRule */ protected $min = 8; + /** + * The maximum size of the password. + * + * @var int + */ + protected $max; + /** * If the password requires at least one uppercase and one lowercase letter. * @@ -193,7 +200,7 @@ public function setData($data) } /** - * Sets the minimum size of the password. + * Set the minimum size of the password. * * @param int $size * @return $this @@ -203,6 +210,19 @@ public static function min($size) return new static($size); } + /** + * Set the maximum size of the password. + * + * @param int $size + * @return $this + */ + public function max($size) + { + $this->max = $size; + + return $this; + } + /** * Ensures the password has not been compromised in data leaks. * @@ -300,6 +320,10 @@ public function passes($attribute, $value) return; } + if ($this->max && mb_strlen($value) > $this->max) { + $validator->addFailure($attribute, 'max.string'); + } + if ($this->mixedCase && ! preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/u', $value)) { $validator->addFailure($attribute, 'password.mixed'); } diff --git a/tests/Validation/ValidationPasswordRuleTest.php b/tests/Validation/ValidationPasswordRuleTest.php index 3cfc7c43911a..8fb9673ebff9 100644 --- a/tests/Validation/ValidationPasswordRuleTest.php +++ b/tests/Validation/ValidationPasswordRuleTest.php @@ -42,6 +42,15 @@ public function testMin() $this->passes(new Password(8), ['88888888']); } + public function testMax() + { + $this->fails(Password::min(2)->max(4), ['aaaaa', '11111111'], [ + 'validation.max.string', + ]); + + $this->passes(Password::min(2)->max(3), ['aa', '111']); + } + public function testConditional() { $is_privileged_user = true; From ac5bed5b3a1efa194144dee0ce98111aad60c1b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=2E=20Nagy=20Gerg=C5=91?= Date: Sat, 20 Jan 2024 20:40:39 +0100 Subject: [PATCH 022/325] [10.x] Add multiple channels/routes to AnonymousNotifiable at once (#49745) * [10.x] Add multiple channels/routes to AnonymousNotifiable at once * Update Notification.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Support/Facades/Notification.php | 17 +++++++++++++++++ ...gNotificationsViaAnonymousNotifiableTest.php | 14 ++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/Illuminate/Support/Facades/Notification.php b/src/Illuminate/Support/Facades/Notification.php index e82aa0529d65..a588bd5467ed 100644 --- a/src/Illuminate/Support/Facades/Notification.php +++ b/src/Illuminate/Support/Facades/Notification.php @@ -54,6 +54,23 @@ public static function fake() }); } + /** + * Begin sending a notification to an anonymous notifiable on the given channels. + * + * @param array $channels + * @return \Illuminate\Notifications\AnonymousNotifiable + */ + public static function routes(array $channels) + { + $notifiable = new AnonymousNotifiable; + + foreach ($channels as $channel => $route) { + $notifiable->route($channel, $route); + } + + return $notifiable; + } + /** * Begin sending a notification to an anonymous notifiable. * diff --git a/tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php b/tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php index d48941336c86..38f7f252bdbf 100644 --- a/tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php +++ b/tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php @@ -28,6 +28,20 @@ public function testMailIsSent() ], $_SERVER['__notifiable.route']); } + public function testAnonymousNotifiableWithMultipleRoutes() + { + $_SERVER['__notifiable.route'] = []; + + NotificationFacade::routes([ + 'testchannel' => 'enzo', + 'anothertestchannel' => 'enzo@deepblue.com', + ])->notify(new TestMailNotificationForAnonymousNotifiable()); + + $this->assertEquals([ + 'enzo', 'enzo@deepblue.com', + ], $_SERVER['__notifiable.route']); + } + public function testFaking() { $fake = NotificationFacade::fake(); From 37eea841d5955ed3902d8660d84c15da400755f1 Mon Sep 17 00:00:00 2001 From: Maarten Buis Date: Sun, 21 Jan 2024 18:01:45 +0100 Subject: [PATCH 023/325] [10x] Sort service providers alphabetically (#49762) --- src/Illuminate/Support/DefaultProviders.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/DefaultProviders.php b/src/Illuminate/Support/DefaultProviders.php index ef7422fd6239..395b7cb9ec43 100644 --- a/src/Illuminate/Support/DefaultProviders.php +++ b/src/Illuminate/Support/DefaultProviders.php @@ -33,10 +33,10 @@ public function __construct(?array $providers = null) \Illuminate\Mail\MailServiceProvider::class, \Illuminate\Notifications\NotificationServiceProvider::class, \Illuminate\Pagination\PaginationServiceProvider::class, + \Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, \Illuminate\Pipeline\PipelineServiceProvider::class, \Illuminate\Queue\QueueServiceProvider::class, \Illuminate\Redis\RedisServiceProvider::class, - \Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, \Illuminate\Session\SessionServiceProvider::class, \Illuminate\Translation\TranslationServiceProvider::class, \Illuminate\Validation\ValidationServiceProvider::class, From b754ca6c258a9622aa2a9fb92f1daa1dbe22549b Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Mon, 22 Jan 2024 09:58:11 +1100 Subject: [PATCH 024/325] [10.x] Global default options for the http factory (#49767) * Global default options for the http factory * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Http/Client/Factory.php | 22 +++++++++++- tests/Http/HttpClientTest.php | 47 ++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Client/Factory.php b/src/Illuminate/Http/Client/Factory.php index d3411ce18777..4bcd13d21b09 100644 --- a/src/Illuminate/Http/Client/Factory.php +++ b/src/Illuminate/Http/Client/Factory.php @@ -36,6 +36,13 @@ class Factory */ protected $globalMiddleware = []; + /** + * The options to apply to every request. + * + * @var array + */ + protected $globalOptions = []; + /** * The stub callables that will handle requests. * @@ -123,6 +130,19 @@ public function globalResponseMiddleware($middleware) return $this; } + /** + * Set the options to apply to every request. + * + * @param array $options + * @return $this + */ + public function globalOptions($options) + { + $this->globalOptions = $options; + + return $this; + } + /** * Create a new response instance for use during stubbing. * @@ -400,7 +420,7 @@ public function recorded($callback = null) */ protected function newPendingRequest() { - return new PendingRequest($this, $this->globalMiddleware); + return (new PendingRequest($this, $this->globalMiddleware))->withOptions($this->globalOptions); } /** diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 6b790aa06534..905c81469e06 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -2680,6 +2680,53 @@ public function testItCanReturnCustomResponseClass(): void $this->assertInstanceOf(TestResponse::class, $response); $this->assertSame('expected content', $response->body()); } + + public function testItCanHaveGlobalDefaultValues() + { + $factory = new Factory; + $timeout = null; + $allowRedirects = null; + $headers = null; + $factory->fake(function ($request, $options) use (&$timeout, &$allowRedirects, &$headers, $factory) { + $timeout = $options['timeout']; + $allowRedirects = $options['allow_redirects']; + $headers = $request->headers(); + + return $factory->response(''); + }); + + $factory->get('https://laravel.com'); + $this->assertSame(30, $timeout); + $this->assertSame(['max' => 5, 'protocols' => ['http', 'https'], 'strict' => false, 'referer' => false, 'track_redirects' => false], $allowRedirects); + $this->assertNull($headers['X-Foo'] ?? null); + + $factory->globalOptions([ + 'timeout' => 5, + 'allow_redirects' => false, + 'headers' => [ + 'X-Foo' => 'true', + ], + ]); + + $factory->get('https://laravel.com'); + $this->assertSame(5, $timeout); + $this->assertFalse($allowRedirects); + $this->assertSame(['true'], $headers['X-Foo']); + + $factory->globalOptions([ + 'timeout' => 10, + 'headers' => [ + 'X-Foo' => 'false', + 'X-Bar' => 'true', + ], + ]); + + $factory->get('https://laravel.com'); + $this->assertSame(10, $timeout); + $this->assertSame(['max' => 5, 'protocols' => ['http', 'https'], 'strict' => false, 'referer' => false, 'track_redirects' => false], $allowRedirects); + $this->assertSame(['false'], $headers['X-Foo']); + $this->assertSame(['true'], $headers['X-Bar']); + } } class CustomFactory extends Factory From d4f9dab018ad41adcf3860b1730410f007e686ea Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Sun, 21 Jan 2024 22:58:48 +0000 Subject: [PATCH 025/325] Update facade docblocks --- src/Illuminate/Support/Facades/Http.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index 4c0cbd915eda..ddd8e80b8fcb 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -8,6 +8,7 @@ * @method static \Illuminate\Http\Client\Factory globalMiddleware(callable $middleware) * @method static \Illuminate\Http\Client\Factory globalRequestMiddleware(callable $middleware) * @method static \Illuminate\Http\Client\Factory globalResponseMiddleware(callable $middleware) + * @method static \Illuminate\Http\Client\Factory globalOptions(array $options) * @method static \GuzzleHttp\Promise\PromiseInterface response(array|string|null $body = null, int $status = 200, array $headers = []) * @method static \Illuminate\Http\Client\ResponseSequence sequence(array $responses = []) * @method static \Illuminate\Http\Client\Factory allowStrayRequests() From e364c47680724e8b02b3bfabf8441e4957047feb Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 22 Jan 2024 21:55:20 +0800 Subject: [PATCH 026/325] [10.x] Only use `Carbon` if accessed from Laravel or also uses (#49772) `illuminate/support` fixes #49765 Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Collections/LazyCollection.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index ce84977aad99..e741ba0bf0a0 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -1740,6 +1740,8 @@ protected function passthru($method, array $params) */ protected function now() { - return Carbon::now()->timestamp; + return class_exists(Carbon::class) + ? Carbon::now()->timestamp + : time(); } } From cb9983c3a56e65b967cd4ff59ed75cc2a6949f9c Mon Sep 17 00:00:00 2001 From: Steve Bauman Date: Mon, 22 Jan 2024 11:23:01 -0500 Subject: [PATCH 027/325] [10.x] Add `Str::unwrap` (#49779) * Add Str::unwrap * Add "unwrap" to stringable * Add more tests --- src/Illuminate/Support/Str.php | 21 +++++++++++++++++++++ src/Illuminate/Support/Stringable.php | 12 ++++++++++++ tests/Support/SupportStrTest.php | 9 +++++++++ tests/Support/SupportStringableTest.php | 7 +++++++ 4 files changed, 49 insertions(+) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 4d51462f6d65..fc444ba699c7 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -390,6 +390,27 @@ public static function wrap($value, $before, $after = null) return $before.$value.($after ??= $before); } + /** + * Unwrap the string with the given strings. + * + * @param string $value + * @param string $before + * @param string|null $after + * @return string + */ + public static function unwrap($value, $before, $after = null) + { + if (static::startsWith($value, $before)) { + $value = static::substr($value, static::length($before)); + } + + if (static::endsWith($value, $after ??= $before)) { + $value = static::substr($value, 0, -static::length($after)); + } + + return $value; + } + /** * Determine if a given string matches a given pattern. * diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 144cbb8c5537..cc1157b5d496 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -1224,6 +1224,18 @@ public function wrap($before, $after = null) return new static(Str::wrap($this->value, $before, $after)); } + /** + * Unwrap the string with the given strings. + * + * @param string $before + * @param string|null $after + * @return static + */ + public function unwrap($before, $after = null) + { + return new static(Str::unwrap($this->value, $before, $after)); + } + /** * Convert the string into a `HtmlString` instance. * diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index fbf5eeff6d0d..af0523b935fd 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -425,6 +425,15 @@ public function testWrap() $this->assertEquals('foo-bar-baz', Str::wrap('-bar-', 'foo', 'baz')); } + public function testUnwrap() + { + $this->assertEquals('value', Str::unwrap('"value"', '"')); + $this->assertEquals('value', Str::unwrap('"value', '"')); + $this->assertEquals('value', Str::unwrap('value"', '"')); + $this->assertEquals('bar', Str::unwrap('foo-bar-baz', 'foo-', '-baz')); + $this->assertEquals('some: "json"', Str::unwrap('{some: "json"}', '{', '}')); + } + public function testIs() { $this->assertTrue(Str::is('/', '/')); diff --git a/tests/Support/SupportStringableTest.php b/tests/Support/SupportStringableTest.php index 5cf433fa4973..5192a62cc002 100644 --- a/tests/Support/SupportStringableTest.php +++ b/tests/Support/SupportStringableTest.php @@ -1177,6 +1177,13 @@ public function testWrap() $this->assertEquals('"value"', $this->stringable('value')->wrap('"')); } + public function testUnwrap() + { + $this->assertEquals('value', $this->stringable('"value"')->unwrap('"')); + $this->assertEquals('bar', $this->stringable('foo-bar-baz')->unwrap('foo-', '-baz')); + $this->assertEquals('some: "json"', $this->stringable('{some: "json"}')->unwrap('{', '}')); + } + public function testToHtmlString() { $this->assertEquals( From 91bc0f6c0898a311355dff666d640a48f2636ad5 Mon Sep 17 00:00:00 2001 From: Kyle Date: Mon, 22 Jan 2024 19:45:06 +0100 Subject: [PATCH 028/325] Allow Uuid and Ulid in Carbon::createFromId() (#49783) --- src/Illuminate/Support/Carbon.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Carbon.php b/src/Illuminate/Support/Carbon.php index c1a665c7d0f8..343918991a23 100644 --- a/src/Illuminate/Support/Carbon.php +++ b/src/Illuminate/Support/Carbon.php @@ -29,9 +29,11 @@ public static function setTestNow($testNow = null) */ public static function createFromId($id) { - return Ulid::isValid($id) - ? static::createFromInterface(Ulid::fromString($id)->getDateTime()) - : static::createFromInterface(Uuid::fromString($id)->getDateTime()); + if (is_string($id)) { + $id = Ulid::isValid($id) ? Ulid::fromString($id) : Uuid::fromString($id); + } + + return static::createFromInterface($id->getDateTime()); } /** From d7616a176afc641e9693266920cb11e84c352241 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 23 Jan 2024 06:38:08 +0800 Subject: [PATCH 029/325] [10.x] Test Improvements (#49785) extracted from #49764 Signed-off-by: Mior Muhammad Zaki Co-authored-by: kylekatarnls --- .../Console/Scheduling/SubMinuteSchedulingTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php b/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php index bc7b98d5b1b4..05f220576f79 100644 --- a/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php +++ b/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php @@ -101,7 +101,7 @@ public function test_sub_minute_scheduling_can_be_interrupted() Sleep::whenFakingSleep(function ($duration) use ($startedAt) { Carbon::setTestNow(now()->add($duration)); - if (now()->diffInSeconds($startedAt) >= 30) { + if ($startedAt->diffInSeconds() >= 30) { $this->artisan('schedule:interrupt') ->expectsOutputToContain('Broadcasting schedule interrupt signal.'); } @@ -130,11 +130,11 @@ public function test_sub_minute_events_stop_for_the_rest_of_the_minute_once_main Sleep::whenFakingSleep(function ($duration) use ($startedAt) { Carbon::setTestNow(now()->add($duration)); - if (now()->diffInSeconds($startedAt) >= 30 && ! $this->app->isDownForMaintenance()) { + if ($startedAt->diffInSeconds() >= 30 && ! $this->app->isDownForMaintenance()) { $this->artisan('down'); } - if (now()->diffInSeconds($startedAt) >= 40 && $this->app->isDownForMaintenance()) { + if ($startedAt->diffInSeconds() >= 40 && $this->app->isDownForMaintenance()) { $this->artisan('up'); } }); From fef1aff874a6749c44f8e142e5764eab8cb96890 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 23 Jan 2024 09:07:56 -0600 Subject: [PATCH 030/325] version --- 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 a352be0fc7bb..34a5c99f1fc2 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.41.0'; + const VERSION = '10.42.0'; /** * The base path for the Laravel installation. From 882fed2167262881aae16c45e4fce22f9cd5b0b6 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 23 Jan 2024 15:27:29 +0000 Subject: [PATCH 031/325] Update CHANGELOG --- CHANGELOG.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c8689860c49..bc51589166da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.41.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.42.0...10.x) + +## [v10.42.0](https://github.com/laravel/framework/compare/v10.41.0...v10.42.0) - 2024-01-23 + +* [10.x] Switch to hash_equals in `File::hasSameHash()` by [@simonhamp](https://github.com/simonhamp) in https://github.com/laravel/framework/pull/49721 +* [10.x] fix Rule::unless for callable $condition by [@dbakan](https://github.com/dbakan) in https://github.com/laravel/framework/pull/49726 +* [10.x] Adds JobQueueing event by [@dmason30](https://github.com/dmason30) in https://github.com/laravel/framework/pull/49722 +* [10.x] Fix decoding issue in MailLogTransport by [@rojtjo](https://github.com/rojtjo) in https://github.com/laravel/framework/pull/49727 +* [10.x] Implement "max" validation rule for passwords by [@angelej](https://github.com/angelej) in https://github.com/laravel/framework/pull/49739 +* [10.x] Add multiple channels/routes to AnonymousNotifiable at once by [@iamgergo](https://github.com/iamgergo) in https://github.com/laravel/framework/pull/49745 +* [10.x] Sort service providers alphabetically by [@buismaarten](https://github.com/buismaarten) in https://github.com/laravel/framework/pull/49762 +* [10.x] Global default options for the http factory by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/49767 +* [10.x] Only use `Carbon` if accessed from Laravel or also uses `illuminate/support` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49772 +* [10.x] Add `Str::unwrap` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/49779 +* [10.x] Allow Uuid and Ulid in Carbon::createFromId() by [@kylekatarnls](https://github.com/kylekatarnls) in https://github.com/laravel/framework/pull/49783 +* [10.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49785 ## [v10.41.0](https://github.com/laravel/framework/compare/v10.40.0...v10.41.0) - 2024-01-16 From 76e49317a7c8d4d2058b048e7c5bd553f2f7523f Mon Sep 17 00:00:00 2001 From: Mikhail Salkov Date: Wed, 24 Jan 2024 06:56:49 +0500 Subject: [PATCH 032/325] [10.x] Add storage:unlink command (#49795) * Add storage:delete-links command * Rename to storage:unlink * Update StorageUnlinkCommand.php --------- Co-authored-by: Taylor Otwell --- .../Console/StorageUnlinkCommand.php | 53 +++++++++++++++++++ .../Providers/ArtisanServiceProvider.php | 2 + 2 files changed, 55 insertions(+) create mode 100644 src/Illuminate/Foundation/Console/StorageUnlinkCommand.php diff --git a/src/Illuminate/Foundation/Console/StorageUnlinkCommand.php b/src/Illuminate/Foundation/Console/StorageUnlinkCommand.php new file mode 100644 index 000000000000..504bc80ab0ac --- /dev/null +++ b/src/Illuminate/Foundation/Console/StorageUnlinkCommand.php @@ -0,0 +1,53 @@ +links() as $link => $target) { + if (! file_exists($link) || ! is_link($link)) { + continue; + } + + $this->laravel->make('files')->delete($link); + + $this->components->info("The [$link] link has been deleted."); + } + } + + /** + * Get the symbolic links that are configured for the application. + * + * @return array + */ + protected function links() + { + return $this->laravel['config']['filesystems.links'] ?? + [public_path('storage') => storage_path('app/public')]; + } +} diff --git a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php index b92bd4ac91cb..482ccc35f620 100755 --- a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -70,6 +70,7 @@ use Illuminate\Foundation\Console\ScopeMakeCommand; use Illuminate\Foundation\Console\ServeCommand; use Illuminate\Foundation\Console\StorageLinkCommand; +use Illuminate\Foundation\Console\StorageUnlinkCommand; use Illuminate\Foundation\Console\StubPublishCommand; use Illuminate\Foundation\Console\TestMakeCommand; use Illuminate\Foundation\Console\UpCommand; @@ -158,6 +159,7 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid 'ScheduleInterrupt' => ScheduleInterruptCommand::class, 'ShowModel' => ShowModelCommand::class, 'StorageLink' => StorageLinkCommand::class, + 'StorageUnlink' => StorageUnlinkCommand::class, 'Up' => UpCommand::class, 'ViewCache' => ViewCacheCommand::class, 'ViewClear' => ViewClearCommand::class, From f0edf22543d7faa9d34710416e66dd4d22a99adc Mon Sep 17 00:00:00 2001 From: Lito Date: Wed, 24 Jan 2024 15:29:17 +0100 Subject: [PATCH 033/325] Unify method definition commets with \Psr\Logger\Interface (#49805) Since https://github.com/php-fig/log/blob/master/src/LoggerInterface.php allows to use \Stringable as $message, the definition of the methods is updated to phpstan error reports. --- src/Illuminate/Log/LogManager.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Log/LogManager.php b/src/Illuminate/Log/LogManager.php index 15df806d55e6..b3ece35d811c 100644 --- a/src/Illuminate/Log/LogManager.php +++ b/src/Illuminate/Log/LogManager.php @@ -630,7 +630,7 @@ public function getChannels() /** * System is unusable. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -645,7 +645,7 @@ public function emergency($message, array $context = []): void * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -659,7 +659,7 @@ public function alert($message, array $context = []): void * * Example: Application component unavailable, unexpected exception. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -672,7 +672,7 @@ public function critical($message, array $context = []): void * Runtime errors that do not require immediate action but should typically * be logged and monitored. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -687,7 +687,7 @@ public function error($message, array $context = []): void * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -699,7 +699,7 @@ public function warning($message, array $context = []): void /** * Normal but significant events. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -713,7 +713,7 @@ public function notice($message, array $context = []): void * * Example: User logs in, SQL logs. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -725,7 +725,7 @@ public function info($message, array $context = []): void /** * Detailed debug information. * - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ @@ -738,7 +738,7 @@ public function debug($message, array $context = []): void * Logs with an arbitrary level. * * @param mixed $level - * @param string $message + * @param string|\Stringable $message * @param array $context * @return void */ From 740543ce549f0ed270b12c930f61519aa5111dac Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 24 Jan 2024 14:29:53 +0000 Subject: [PATCH 034/325] Update facade docblocks --- src/Illuminate/Support/Facades/Log.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Support/Facades/Log.php b/src/Illuminate/Support/Facades/Log.php index 37e03961ebd5..ba40965286fe 100755 --- a/src/Illuminate/Support/Facades/Log.php +++ b/src/Illuminate/Support/Facades/Log.php @@ -16,15 +16,15 @@ * @method static \Illuminate\Log\LogManager extend(string $driver, \Closure $callback) * @method static void forgetChannel(string|null $driver = null) * @method static array getChannels() - * @method static void emergency(string $message, array $context = []) - * @method static void alert(string $message, array $context = []) - * @method static void critical(string $message, array $context = []) - * @method static void error(string $message, array $context = []) - * @method static void warning(string $message, array $context = []) - * @method static void notice(string $message, array $context = []) - * @method static void info(string $message, array $context = []) - * @method static void debug(string $message, array $context = []) - * @method static void log(mixed $level, string $message, array $context = []) + * @method static void emergency(string|\Stringable $message, array $context = []) + * @method static void alert(string|\Stringable $message, array $context = []) + * @method static void critical(string|\Stringable $message, array $context = []) + * @method static void error(string|\Stringable $message, array $context = []) + * @method static void warning(string|\Stringable $message, array $context = []) + * @method static void notice(string|\Stringable $message, array $context = []) + * @method static void info(string|\Stringable $message, array $context = []) + * @method static void debug(string|\Stringable $message, array $context = []) + * @method static void log(mixed $level, string|\Stringable $message, array $context = []) * @method static void write(string $level, \Illuminate\Contracts\Support\Arrayable|\Illuminate\Contracts\Support\Jsonable|\Illuminate\Support\Stringable|array|string $message, array $context = []) * @method static \Illuminate\Log\Logger withContext(array $context = []) * @method static void listen(\Closure $callback) From caee0bd8542f8324b2847b1773b8efb00185be61 Mon Sep 17 00:00:00 2001 From: Eliezer Margareten <46111162+emargareten@users.noreply.github.com> Date: Wed, 24 Jan 2024 16:33:05 +0200 Subject: [PATCH 035/325] class-name string argument for global scopes (#49802) --- .../Eloquent/Concerns/HasGlobalScopes.php | 4 +++- .../DatabaseEloquentGlobalScopesTest.php | 20 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php b/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php index 5d7047953115..320126366b73 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php @@ -26,9 +26,11 @@ public static function addGlobalScope($scope, $implementation = null) return static::$globalScopes[static::class][spl_object_hash($scope)] = $scope; } elseif ($scope instanceof Scope) { return static::$globalScopes[static::class][get_class($scope)] = $scope; + } elseif (is_string($scope) && class_exists($scope) && is_subclass_of($scope, Scope::class)) { + return static::$globalScopes[static::class][$scope] = new $scope; } - throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope.'); + throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope or be a class name of a class extending '.Scope::class); } /** diff --git a/tests/Database/DatabaseEloquentGlobalScopesTest.php b/tests/Database/DatabaseEloquentGlobalScopesTest.php index 7ae26071506d..a1bb15b3c4a1 100644 --- a/tests/Database/DatabaseEloquentGlobalScopesTest.php +++ b/tests/Database/DatabaseEloquentGlobalScopesTest.php @@ -43,6 +43,14 @@ public function testGlobalScopeCanBeRemoved() $this->assertEquals([], $query->getBindings()); } + public function testClassNameGlobalScopeIsApplied() + { + $model = new EloquentClassNameGlobalScopesTestModel; + $query = $model->newQuery(); + $this->assertSame('select * from "table" where "active" = ?', $query->toSql()); + $this->assertEquals([1], $query->getBindings()); + } + public function testClosureGlobalScopeIsApplied() { $model = new EloquentClosureGlobalScopesTestModel; @@ -190,6 +198,18 @@ public static function boot() } } +class EloquentClassNameGlobalScopesTestModel extends Model +{ + protected $table = 'table'; + + public static function boot() + { + static::addGlobalScope(ActiveScope::class); + + parent::boot(); + } +} + class ActiveScope implements Scope { public function apply(Builder $builder, Model $model) From 8fd5c3586c3b76c716c33b20df5b4851eeca6f0f Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Wed, 24 Jan 2024 18:11:29 +0330 Subject: [PATCH 036/325] [10.x] Add `hasIndex()` and minor Schema enhancements (#49796) * add hasIndex, getIndexListing and getTableListing * minor schema enhancements * fix tests * Update Builder.php --------- Co-authored-by: Taylor Otwell --- .../Query/Processors/MySqlProcessor.php | 2 +- src/Illuminate/Database/Schema/Builder.php | 59 +++++++++++++++++++ .../Database/Schema/ColumnDefinition.php | 2 +- .../Schema/Grammars/PostgresGrammar.php | 2 +- src/Illuminate/Support/Facades/Schema.php | 3 + .../DatabaseSQLiteSchemaGrammarTest.php | 8 +-- .../DatabaseSchemaBuilderIntegrationTest.php | 10 +--- .../Database/SchemaBuilderTest.php | 16 ++++- 8 files changed, 85 insertions(+), 17 deletions(-) diff --git a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php index 091fc80b52b0..07553c39195b 100644 --- a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php +++ b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php @@ -38,7 +38,7 @@ public function processColumns($results) 'nullable' => $result->nullable === 'YES', 'default' => $result->default, 'auto_increment' => $result->extra === 'auto_increment', - 'comment' => $result->comment, + 'comment' => $result->comment ?: null, ]; }, $results); } diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 770c6c52655c..9cb687f97e8a 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -202,6 +202,16 @@ public function getTables() ); } + /** + * Get the names of the tables that belong to the database. + * + * @return array + */ + public function getTableListing() + { + return array_column($this->getTables(), 'name'); + } + /** * Get the views that belong to the database. * @@ -370,6 +380,55 @@ public function getIndexes($table) ); } + /** + * Get the names of the indexes for a given table. + * + * @param string $table + * @return array + */ + public function getIndexListing($table) + { + return array_column($this->getIndexes($table), 'name'); + } + + /** + * Determine if the given table has a given index. + * + * @param string $table + * @param string|array $index + * @param string|null $type + * @return bool + */ + public function hasIndex($table, $index, $type = null) + { + $type = is_null($type) ? $type : strtolower($type); + + if (is_array($index)) { + sort($index); + } + + foreach ($this->getIndexes($table) as $value) { + $typeMatches = is_null($type) + || ($type === 'primary' && $value['primary']) + || ($type === 'unique' && $value['unique']) + || $type === $value['type']; + + if ($value['name'] === $index && $typeMatches) { + return true; + } + + if (is_array($index)) { + sort($value['columns']); + + if ($value['columns'] === $index && $typeMatches) { + return true; + } + } + } + + return false; + } + /** * Get the foreign keys for a given table. * diff --git a/src/Illuminate/Database/Schema/ColumnDefinition.php b/src/Illuminate/Database/Schema/ColumnDefinition.php index 51265ac4213e..1a7e638836b2 100644 --- a/src/Illuminate/Database/Schema/ColumnDefinition.php +++ b/src/Illuminate/Database/Schema/ColumnDefinition.php @@ -15,7 +15,7 @@ * @method $this default(mixed $value) Specify a "default" value for the column * @method $this first() Place the column "first" in the table (MySQL) * @method $this from(int $startingValue) Set the starting value of an auto-incrementing field (MySQL / PostgreSQL) - * @method $this generatedAs(string|Expression $expression = null) Create a SQL compliant identity column (PostgreSQL) + * @method $this generatedAs(string|\Illuminate\Database\Query\Expression $expression = null) Create a SQL compliant identity column (PostgreSQL) * @method $this index(string $indexName = null) Add an index * @method $this invisible() Specify that the column should be invisible to "SELECT *" (MySQL) * @method $this nullable(bool $value = true) Allow NULL values to be inserted into the column diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index acc9c5800715..6119607abd76 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -87,7 +87,7 @@ public function compileTables() { return 'select c.relname as name, n.nspname as schema, pg_total_relation_size(c.oid) as size, ' ."obj_description(c.oid, 'pg_class') as comment from pg_class c, pg_namespace n " - ."where c.relkind in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema')" + ."where c.relkind in ('r', 'p') and n.oid = c.relnamespace and n.nspname not in ('pg_catalog', 'information_schema') " .'order by c.relname'; } diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 8aa0eb900a55..5ac399ac1b9a 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -12,6 +12,7 @@ * @method static bool dropDatabaseIfExists(string $name) * @method static bool hasTable(string $table) * @method static bool hasView(string $view) + * @method static bool hasIndex(string $table, string|array $index, string|null $type = null) * @method static array getTables() * @method static array getViews() * @method static array getTypes() @@ -20,7 +21,9 @@ * @method static void whenTableHasColumn(string $table, string $column, \Closure $callback) * @method static void whenTableDoesntHaveColumn(string $table, string $column, \Closure $callback) * @method static string getColumnType(string $table, string $column, bool $fullDefinition = false) + * @method static array getTableListing() * @method static array getColumnListing(string $table) + * @method static array getIndexListing(string $table) * @method static array getColumns(string $table) * @method static array getIndexes(string $table) * @method static array getForeignKeys(string $table) diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index 60339647c0f9..409c28ccd637 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -164,7 +164,7 @@ public function testRenameIndex() $table->index(['name', 'email'], 'index1'); }); - $indexes = array_column($schema->getIndexes('users'), 'name'); + $indexes = $schema->getIndexListing('users'); $this->assertContains('index1', $indexes); $this->assertNotContains('index2', $indexes); @@ -173,10 +173,8 @@ public function testRenameIndex() $table->renameIndex('index1', 'index2'); }); - $indexes = $schema->getIndexes('users'); - - $this->assertNotContains('index1', array_column($indexes, 'name')); - $this->assertTrue(collect($indexes)->contains( + $this->assertFalse($schema->hasIndex('users', 'index1')); + $this->assertTrue(collect($schema->getIndexes('users'))->contains( fn ($index) => $index['name'] === 'index2' && $index['columns'] === ['name', 'email'] )); } diff --git a/tests/Database/DatabaseSchemaBuilderIntegrationTest.php b/tests/Database/DatabaseSchemaBuilderIntegrationTest.php index ce261ab4e94d..80734d25fef8 100644 --- a/tests/Database/DatabaseSchemaBuilderIntegrationTest.php +++ b/tests/Database/DatabaseSchemaBuilderIntegrationTest.php @@ -87,10 +87,7 @@ public function testHasColumnAndIndexWithPrefixIndexDisabled() $table->string('name')->index(); }); - $this->assertContains( - 'table1_name_index', - array_column($this->db->connection()->getSchemaBuilder()->getIndexes('table1'), 'name') - ); + $this->assertTrue($this->db->connection()->getSchemaBuilder()->hasIndex('table1', 'table1_name_index')); } public function testHasColumnAndIndexWithPrefixIndexEnabled() @@ -107,10 +104,7 @@ public function testHasColumnAndIndexWithPrefixIndexEnabled() $table->string('name')->index(); }); - $this->assertContains( - 'example_table1_name_index', - array_column($this->db->connection()->getSchemaBuilder()->getIndexes('table1'), 'name') - ); + $this->assertTrue($this->db->connection()->getSchemaBuilder()->hasIndex('table1', 'example_table1_name_index')); } public function testDropColumnWithTablePrefix() diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index fbd42e13c489..7a75d62b5fe0 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -193,15 +193,20 @@ public function testGetAndDropTypes() DB::statement("create type enum_foo as enum ('new', 'open', 'closed')"); DB::statement('create type range_foo as range (subtype = float8)'); DB::statement('create domain domain_foo as text'); + DB::statement('create type base_foo'); + DB::statement("create function foo_in(cstring) returns base_foo language internal immutable strict parallel safe as 'int2in'"); + DB::statement("create function foo_out(base_foo) returns cstring language internal immutable strict parallel safe as 'int2out'"); + DB::statement('create type base_foo (input = foo_in, output = foo_out)'); $types = Schema::getTypes(); - $this->assertCount(11, $types); + $this->assertCount(13, $types); $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'pseudo_foo' && $type['type'] === 'pseudo' && ! $type['implicit'])); $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'comp_foo' && $type['type'] === 'composite' && ! $type['implicit'])); $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'enum_foo' && $type['type'] === 'enum' && ! $type['implicit'])); $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'range_foo' && $type['type'] === 'range' && ! $type['implicit'])); $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'domain_foo' && $type['type'] === 'domain' && ! $type['implicit'])); + $this->assertTrue(collect($types)->contains(fn ($type) => $type['name'] === 'base_foo' && $type['type'] === 'base' && ! $type['implicit'])); Schema::dropAllTypes(); $types = Schema::getTypes(); @@ -256,6 +261,10 @@ public function testGetIndexes() && ! $indexes[0]['unique'] && ! $indexes[0]['primary'] ); + $this->assertTrue(Schema::hasIndex('foo', 'my_index')); + $this->assertTrue(Schema::hasIndex('foo', ['bar'])); + $this->assertFalse(Schema::hasIndex('foo', 'my_index', 'primary')); + $this->assertFalse(Schema::hasIndex('foo', ['bar'], 'unique')); } public function testGetUniqueIndexes() @@ -277,6 +286,11 @@ public function testGetUniqueIndexes() $this->assertTrue(collect($indexes)->contains( fn ($index) => $index['name'] === 'foo_baz_bar_unique' && $index['columns'] === ['baz', 'bar'] && $index['unique'] )); + $this->assertTrue(Schema::hasIndex('foo', 'foo_baz_bar_unique')); + $this->assertTrue(Schema::hasIndex('foo', 'foo_baz_bar_unique', 'unique')); + $this->assertTrue(Schema::hasIndex('foo', ['bar', 'baz'])); + $this->assertTrue(Schema::hasIndex('foo', ['bar', 'baz'], 'unique')); + $this->assertFalse(Schema::hasIndex('foo', ['bar', 'baz'], 'primary')); } public function testGetIndexesWithCompositeKeys() From cad642e9632b5dd2e400c26842763c3758860710 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 24 Jan 2024 14:42:01 +0000 Subject: [PATCH 037/325] Update facade docblocks --- src/Illuminate/Support/Facades/Schema.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index 5ac399ac1b9a..b59fbf582ce9 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -12,8 +12,8 @@ * @method static bool dropDatabaseIfExists(string $name) * @method static bool hasTable(string $table) * @method static bool hasView(string $view) - * @method static bool hasIndex(string $table, string|array $index, string|null $type = null) * @method static array getTables() + * @method static array getTableListing() * @method static array getViews() * @method static array getTypes() * @method static bool hasColumn(string $table, string $column) @@ -21,11 +21,11 @@ * @method static void whenTableHasColumn(string $table, string $column, \Closure $callback) * @method static void whenTableDoesntHaveColumn(string $table, string $column, \Closure $callback) * @method static string getColumnType(string $table, string $column, bool $fullDefinition = false) - * @method static array getTableListing() * @method static array getColumnListing(string $table) - * @method static array getIndexListing(string $table) * @method static array getColumns(string $table) * @method static array getIndexes(string $table) + * @method static array getIndexListing(string $table) + * @method static bool hasIndex(string $table, string|array $index, string|null $type = null) * @method static array getForeignKeys(string $table) * @method static void table(string $table, \Closure $callback) * @method static void create(string $table, \Closure $callback) From 5f29723a30d0141f2de1b3eb25aa7632a820e829 Mon Sep 17 00:00:00 2001 From: Mateus Junges Date: Wed, 24 Jan 2024 14:14:45 -0300 Subject: [PATCH 038/325] [10.x] Do not touch `BelongsToMany` relation when using `withoutTouching` (#49798) * Do not touch relation when ignore touching is enabled * Add tests * Apply styleci fixes --- .../Eloquent/Relations/BelongsToMany.php | 4 ++ ...oquentBelongsToManyWithoutTouchingTest.php | 65 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 tests/Database/DatabaseEloquentBelongsToManyWithoutTouchingTest.php diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 37c698f3d80f..c0459b4d7267 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -1179,6 +1179,10 @@ protected function guessInverseRelation() */ public function touch() { + if ($this->related->isIgnoringTouch()) { + return; + } + $columns = [ $this->related->getUpdatedAtColumn() => $this->related->freshTimestampString(), ]; diff --git a/tests/Database/DatabaseEloquentBelongsToManyWithoutTouchingTest.php b/tests/Database/DatabaseEloquentBelongsToManyWithoutTouchingTest.php new file mode 100644 index 000000000000..32d617e36fb2 --- /dev/null +++ b/tests/Database/DatabaseEloquentBelongsToManyWithoutTouchingTest.php @@ -0,0 +1,65 @@ +makePartial(); + $related->shouldReceive('getUpdatedAtColumn')->never(); + $related->shouldReceive('freshTimestampString')->never(); + + $this->assertFalse($related::isIgnoringTouch()); + + Model::withoutTouching(function () use ($related) { + $this->assertTrue($related::isIgnoringTouch()); + + $builder = m::mock(Builder::class); + $builder->shouldReceive('join'); + $parent = m::mock(User::class); + + $parent->shouldReceive('getAttribute')->with('id')->andReturn(1); + $builder->shouldReceive('getModel')->andReturn($related); + $builder->shouldReceive('where'); + $relation = new BelongsToMany($builder, $parent, 'article_users', 'user_id', 'article_id', 'id', 'id'); + $builder->shouldReceive('update')->never(); + + $relation->touch(); + }); + + $this->assertFalse($related::isIgnoringTouch()); + } +} + +class User extends Model +{ + protected $table = 'users'; + protected $fillable = ['id', 'email']; + + public function articles(): BelongsToMany + { + return $this->belongsToMany(Article::class, 'article_user', 'user_id', 'article_id'); + } +} + +class Article extends Model +{ + protected $table = 'articles'; + protected $fillable = ['id', 'title']; + protected $touches = ['user']; + + public function users(): BelongsToMany + { + return $this->belongsToMany(User::class, 'article_user', 'article_id', 'user_id'); + } +} From 4247b594924bc84ec37c5b0a9f0feb92d3de299b Mon Sep 17 00:00:00 2001 From: Julio J Date: Wed, 24 Jan 2024 14:15:21 -0300 Subject: [PATCH 039/325] Check properties on mailables are initialized before sharing with the view (#49813) --- src/Illuminate/Mail/Mailable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Mail/Mailable.php b/src/Illuminate/Mail/Mailable.php index 4016ee5a210e..48831c0e551a 100644 --- a/src/Illuminate/Mail/Mailable.php +++ b/src/Illuminate/Mail/Mailable.php @@ -347,7 +347,7 @@ public function buildViewData() } foreach ((new ReflectionClass($this))->getProperties(ReflectionProperty::IS_PUBLIC) as $property) { - if ($property->getDeclaringClass()->getName() !== self::class) { + if ($property->isInitialized($this) && $property->getDeclaringClass()->getName() !== self::class) { $data[$property->getName()] = $property->getValue($this); } } From fdef48d79a7eb8d60bbf61f06fba7138d93dc900 Mon Sep 17 00:00:00 2001 From: Julius Kiekbusch Date: Thu, 25 Jan 2024 11:14:39 +0100 Subject: [PATCH 040/325] Remove duplicate Checkout (#49828) --- .github/workflows/queues.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml index 68e41cfa2aca..d8f37ab3cac7 100644 --- a/.github/workflows/queues.yml +++ b/.github/workflows/queues.yml @@ -128,10 +128,10 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - - - uses: actions/checkout@v3 + - name: Download & Extract beanstalkd run: curl -L https://github.com/beanstalkd/beanstalkd/archive/refs/tags/v1.13.tar.gz | tar xz + - name: Make beanstalkd run: make working-directory: beanstalkd-1.13 From d79ef09fb8b597ebe3719850fd0261ff17ff91bb Mon Sep 17 00:00:00 2001 From: Trevor Morris Date: Thu, 25 Jan 2024 17:00:28 +0000 Subject: [PATCH 041/325] [10.x] Add `insertOrIgnoreUsing` for Eloquent (#49827) * fix: add insertOrIgnoreUsing method * test: add tests for insertOrIgnoreUsing * test: fix misspelled ignore using methods * test: fix insert or ignore argument count and mock getDatabaseName * test: fix sql quotes for mysql * test: fix binding for mysql insert or ignore using --- src/Illuminate/Database/Eloquent/Builder.php | 1 + src/Illuminate/Database/Query/Builder.php | 19 +++ .../Database/Query/Grammars/Grammar.php | 15 ++ .../Database/Query/Grammars/MySqlGrammar.php | 13 ++ .../Query/Grammars/PostgresGrammar.php | 13 ++ .../Database/Query/Grammars/SQLiteGrammar.php | 13 ++ .../Database/DatabaseEloquentBuilderTest.php | 5 + tests/Database/DatabaseQueryBuilderTest.php | 131 ++++++++++++++++++ 8 files changed, 210 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 87c06c2f96fe..5563b5477a2b 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -113,6 +113,7 @@ class Builder implements BuilderContract 'insertgetid', 'insertorignore', 'insertusing', + 'insertorignoreusing', 'max', 'min', 'raw', diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 62abc80023c8..ebb833707b11 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -3406,6 +3406,25 @@ public function insertUsing(array $columns, $query) ); } + /** + * Insert new records into the table using a subquery while ignoring errors. + * + * @param array $columns + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @return int + */ + public function insertOrIgnoreUsing(array $columns, $query) + { + $this->applyBeforeQueryCallbacks(); + + [$sql, $bindings] = $this->createSub($query); + + return $this->connection->affectingStatement( + $this->grammar->compileInsertOrIgnoreUsing($this, $columns, $sql), + $this->cleanBindings($bindings) + ); + } + /** * Update records in the database. * diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index 3b4f117693f6..a03cdcb03346 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -1090,6 +1090,21 @@ public function compileInsertUsing(Builder $query, array $columns, string $sql) return "insert into {$table} ({$this->columnize($columns)}) $sql"; } + /** + * Compile an insert ignore statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + * + * @throws \RuntimeException + */ + public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql) + { + throw new RuntimeException('This database engine does not support inserting while ignoring errors.'); + } + /** * Compile an update statement into SQL. * diff --git a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php index b9d2a624bd9a..1365d8efe620 100755 --- a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php @@ -106,6 +106,19 @@ public function compileInsertOrIgnore(Builder $query, array $values) return Str::replaceFirst('insert', 'insert ignore', $this->compileInsert($query, $values)); } + /** + * Compile an insert ignore statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + */ + public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql) + { + return Str::replaceFirst('insert', 'insert ignore', $this->compileInsertUsing($query, $columns, $sql)); + } + /** * Compile a "JSON contains" statement into SQL. * diff --git a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php index b1786e5111cc..b39a20a0a5d0 100755 --- a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php @@ -323,6 +323,19 @@ public function compileInsertOrIgnore(Builder $query, array $values) return $this->compileInsert($query, $values).' on conflict do nothing'; } + /** + * Compile an insert ignore statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + */ + public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql) + { + return $this->compileInsertUsing($query, $columns, $sql).' on conflict do nothing'; + } + /** * Compile an insert and get ID statement into SQL. * diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index b628d70d2b02..9a61a69c7108 100755 --- a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -212,6 +212,19 @@ public function compileInsertOrIgnore(Builder $query, array $values) return Str::replaceFirst('insert', 'insert or ignore', $this->compileInsert($query, $values)); } + /** + * Compile an insert ignore statement using a subquery into SQL. + * + * @param \Illuminate\Database\Query\Builder $query + * @param array $columns + * @param string $sql + * @return string + */ + public function compileInsertOrIgnoreUsing(Builder $query, array $columns, string $sql) + { + return Str::replaceFirst('insert', 'insert or ignore', $this->compileInsertUsing($query, $columns, $sql)); + } + /** * Compile the columns for an update statement. * diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 31354f2c1ab0..48e3569738b8 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -918,6 +918,11 @@ public function testQueryPassThru() $this->assertSame('foo', $builder->insertOrIgnore(['bar'])); + $builder = $this->getBuilder(); + $builder->getQuery()->shouldReceive('insertOrIgnoreUsing')->once()->with(['bar'], 'baz')->andReturn('foo'); + + $this->assertSame('foo', $builder->insertOrIgnoreUsing(['bar'], 'baz')); + $builder = $this->getBuilder(); $builder->getQuery()->shouldReceive('insertGetId')->once()->with(['bar'])->andReturn('foo'); diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 011e22c99afb..cf0c8389b09b 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -2893,6 +2893,137 @@ public function testSqlServerInsertOrIgnoreMethod() $builder->from('users')->insertOrIgnore(['email' => 'foo']); } + public function testInsertOrIgnoreUsingMethod() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('does not support'); + $builder = $this->getBuilder(); + $builder->from('users')->insertOrIgnoreUsing(['email' => 'foo'], 'bar'); + } + + public function testSqlServerInsertOrIgnoreUsingMethod() + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('does not support'); + $builder = $this->getSqlServerBuilder(); + $builder->from('users')->insertOrIgnoreUsing(['email' => 'foo'], 'bar'); + } + + public function testMySqlInsertOrIgnoreUsingMethod() + { + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert ignore into `table1` (`foo`) select `bar` from `table2` where `foreign_id` = ?', [0 => 5])->andReturn(1); + + $result = $builder->from('table1')->insertOrIgnoreUsing( + ['foo'], + function (Builder $query) { + $query->select(['bar'])->from('table2')->where('foreign_id', '=', 5); + } + ); + + $this->assertEquals(1, $result); + } + + public function testMySqlInsertOrIgnoreUsingWithEmptyColumns() + { + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert ignore into `table1` select * from `table2` where `foreign_id` = ?', [0 => 5])->andReturn(1); + + $result = $builder->from('table1')->insertOrIgnoreUsing( + [], + function (Builder $query) { + $query->from('table2')->where('foreign_id', '=', 5); + } + ); + + $this->assertEquals(1, $result); + } + + public function testMySqlInsertOrIgnoreUsingInvalidSubquery() + { + $this->expectException(InvalidArgumentException::class); + $builder = $this->getMySqlBuilder(); + $builder->from('table1')->insertOrIgnoreUsing(['foo'], ['bar']); + } + + public function testPostgresInsertOrIgnoreUsingMethod() + { + $builder = $this->getPostgresBuilder(); + $builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert into "table1" ("foo") select "bar" from "table2" where "foreign_id" = ? on conflict do nothing', [5])->andReturn(1); + + $result = $builder->from('table1')->insertOrIgnoreUsing( + ['foo'], + function (Builder $query) { + $query->select(['bar'])->from('table2')->where('foreign_id', '=', 5); + } + ); + + $this->assertEquals(1, $result); + } + + public function testPostgresInsertOrIgnoreUsingWithEmptyColumns() + { + $builder = $this->getPostgresBuilder(); + $builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert into "table1" select * from "table2" where "foreign_id" = ? on conflict do nothing', [5])->andReturn(1); + + $result = $builder->from('table1')->insertOrIgnoreUsing( + [], + function (Builder $query) { + $query->from('table2')->where('foreign_id', '=', 5); + } + ); + + $this->assertEquals(1, $result); + } + + public function testPostgresInsertOrIgnoreUsingInvalidSubquery() + { + $this->expectException(InvalidArgumentException::class); + $builder = $this->getPostgresBuilder(); + $builder->from('table1')->insertOrIgnoreUsing(['foo'], ['bar']); + } + + public function testSQLiteInsertOrIgnoreUsingMethod() + { + $builder = $this->getSQLiteBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert or ignore into "table1" ("foo") select "bar" from "table2" where "foreign_id" = ?', [5])->andReturn(1); + + $result = $builder->from('table1')->insertOrIgnoreUsing( + ['foo'], + function (Builder $query) { + $query->select(['bar'])->from('table2')->where('foreign_id', '=', 5); + } + ); + + $this->assertEquals(1, $result); + } + + public function testSQLiteInsertOrIgnoreUsingWithEmptyColumns() + { + $builder = $this->getSQLiteBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->getConnection()->shouldReceive('affectingStatement')->once()->with('insert or ignore into "table1" select * from "table2" where "foreign_id" = ?', [5])->andReturn(1); + + $result = $builder->from('table1')->insertOrIgnoreUsing( + [], + function (Builder $query) { + $query->from('table2')->where('foreign_id', '=', 5); + } + ); + + $this->assertEquals(1, $result); + } + + public function testSQLiteInsertOrIgnoreUsingInvalidSubquery() + { + $this->expectException(InvalidArgumentException::class); + $builder = $this->getSQLiteBuilder(); + $builder->from('table1')->insertOrIgnoreUsing(['foo'], ['bar']); + } + public function testInsertGetIdMethod() { $builder = $this->getBuilder(); From 9e88fe502c6579e9ef103708e8effd3930d41392 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Thu, 25 Jan 2024 21:09:41 +0330 Subject: [PATCH 042/325] make `hasIndex` order-sensative (#49840) --- src/Illuminate/Database/Schema/Builder.php | 14 +--------- .../Database/SchemaBuilderTest.php | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 9cb687f97e8a..51b97f690618 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -403,27 +403,15 @@ public function hasIndex($table, $index, $type = null) { $type = is_null($type) ? $type : strtolower($type); - if (is_array($index)) { - sort($index); - } - foreach ($this->getIndexes($table) as $value) { $typeMatches = is_null($type) || ($type === 'primary' && $value['primary']) || ($type === 'unique' && $value['unique']) || $type === $value['type']; - if ($value['name'] === $index && $typeMatches) { + if (($value['name'] === $index || $value['columns'] === $index) && $typeMatches) { return true; } - - if (is_array($index)) { - sort($value['columns']); - - if ($value['columns'] === $index && $typeMatches) { - return true; - } - } } return false; diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index 7a75d62b5fe0..6fd62e399cfc 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -288,9 +288,9 @@ public function testGetUniqueIndexes() )); $this->assertTrue(Schema::hasIndex('foo', 'foo_baz_bar_unique')); $this->assertTrue(Schema::hasIndex('foo', 'foo_baz_bar_unique', 'unique')); - $this->assertTrue(Schema::hasIndex('foo', ['bar', 'baz'])); - $this->assertTrue(Schema::hasIndex('foo', ['bar', 'baz'], 'unique')); - $this->assertFalse(Schema::hasIndex('foo', ['bar', 'baz'], 'primary')); + $this->assertTrue(Schema::hasIndex('foo', ['baz', 'bar'])); + $this->assertTrue(Schema::hasIndex('foo', ['baz', 'bar'], 'unique')); + $this->assertFalse(Schema::hasIndex('foo', ['baz', 'bar'], 'primary')); } public function testGetIndexesWithCompositeKeys() @@ -335,6 +335,26 @@ public function testGetFullTextIndexes() $this->assertTrue(collect($indexes)->contains('name', 'articles_body_title_fulltext')); } + public function testHasIndexOrder() + { + Schema::create('foo', function (Blueprint $table) { + $table->integer('bar'); + $table->integer('baz'); + $table->integer('qux'); + + $table->unique(['bar', 'baz']); + $table->index(['baz', 'bar']); + $table->index(['baz', 'qux']); + }); + + $this->assertTrue(Schema::hasIndex('foo', ['bar', 'baz'])); + $this->assertTrue(Schema::hasIndex('foo', ['bar', 'baz'], 'unique')); + $this->assertTrue(Schema::hasIndex('foo', ['baz', 'bar'])); + $this->assertFalse(Schema::hasIndex('foo', ['baz', 'bar'], 'unique')); + $this->assertTrue(Schema::hasIndex('foo', ['baz', 'qux'])); + $this->assertFalse(Schema::hasIndex('foo', ['qux', 'baz'])); + } + public function testGetForeignKeys() { Schema::create('users', function (Blueprint $table) { From 4ecc1da5075087ebc98a0f7444c0f4a2d694de70 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 25 Jan 2024 19:34:44 +0100 Subject: [PATCH 043/325] [10.x] Release action (#49838) * Release action v1 * Release action v2 * Revert "Release action v2" This reverts commit dba605e9ea2dfe675d6fc34e8eb6ab5abbc2c40e. * wip --- .github/workflows/releases.yml | 66 ++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/releases.yml diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml new file mode 100644 index 000000000000..f8c70c8122bf --- /dev/null +++ b/.github/workflows/releases.yml @@ -0,0 +1,66 @@ +name: manual release + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to release' + required: true + +jobs: + release: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Remove optional "v" prefix + id: version + run: VERSION=${{ inputs.version }} echo "::set-output name=version::${VERSION#v}" + + - name: Update Application.php version + run: | + sed -i "s/const VERSION = '.*';/const VERSION = '${{ steps.version.outputs.version }}';/g" src/Illuminate/Foundation/Application.php + git add src/Illuminate/Foundation/Application.php + git commit -m "Update version to v${{ steps.version.outputs.version }}" + git push origin ${{ github.ref_name }} + + - name: SSH into splitter server + uses: appleboy/ssh-action@master + with: + host: 104.248.56.26 + username: forge + key: ${{ secrets.SSH_PRIVATE_KEY_SPLITTER }} + + - name: Change directory + run: cd laravel-${{ github.ref_name }} + + - name: Pull in latest changes + run: git pull origin ${{ github.ref_name }} + + - name: Run release script + run: bash bin/release v${{ steps.version.outputs.version }} + + - name: Generate Release Notes + id: notes + uses: RedCrafter07/release-notes-action@main + with: + tag-name: v${{ steps.version.outputs.version }} + token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref_name }} + + - name: Cleanup release notes + run: | + sed -i '/## What\'\''s Changed/q' ${{ steps.notes.outputs.release-notes }} + sed -i '/## Contributors/,$d' ${{ steps.notes.outputs.release-notes }} + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.version.outputs.version }} + release_name: v${{ steps.version.outputs.version }} + body: ${{ steps.notes.outputs.release-notes }} From 779d2bb14a252496e1226b4aed2c654eec049412 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 25 Jan 2024 17:46:31 -0600 Subject: [PATCH 044/325] wip --- .github/workflows/releases.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index f8c70c8122bf..62a97f54b6c8 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -42,7 +42,7 @@ jobs: - name: Run release script run: bash bin/release v${{ steps.version.outputs.version }} - - name: Generate Release Notes + - name: Generate release notes id: notes uses: RedCrafter07/release-notes-action@main with: @@ -55,7 +55,7 @@ jobs: sed -i '/## What\'\''s Changed/q' ${{ steps.notes.outputs.release-notes }} sed -i '/## Contributors/,$d' ${{ steps.notes.outputs.release-notes }} - - name: Create Release + - name: Create release id: create_release uses: actions/create-release@v1 env: From d293d99709d076c5667462a0b5f6e76ef52c65a4 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 25 Jan 2024 17:47:23 -0600 Subject: [PATCH 045/325] wip --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5cda208db8ec..c186ecd54e48 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,14 +62,14 @@ jobs: REDIS_CONFIGURE_OPTS: --enable-redis --enable-redis-igbinary --enable-redis-msgpack --enable-redis-lzf --with-liblzf --enable-redis-zstd --with-libzstd --enable-redis-lz4 --with-liblz4 REDIS_LIBS: liblz4-dev, liblzf-dev, libzstd-dev - - name: Set Minimum PHP 8.1 Versions + - name: Set minimum PHP 8.1 versions uses: nick-fields/retry@v2 with: timeout_minutes: 5 max_attempts: 5 command: composer require symfony/css-selector:^6.0 --no-interaction --no-update - - name: Set Minimum PHP 8.2 Versions + - name: Set minimum PHP 8.2 versions uses: nick-fields/retry@v2 with: timeout_minutes: 5 From 3bbb50ea7e4d25a6f755749bb69fd545f19a7f34 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 26 Jan 2024 10:25:35 +0100 Subject: [PATCH 046/325] Add MariaDb1060Platform (#49848) --- src/Illuminate/Database/DBAL/TimestampType.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/DBAL/TimestampType.php b/src/Illuminate/Database/DBAL/TimestampType.php index e702523925a1..b5d9777503d9 100644 --- a/src/Illuminate/Database/DBAL/TimestampType.php +++ b/src/Illuminate/Database/DBAL/TimestampType.php @@ -6,6 +6,7 @@ use Doctrine\DBAL\Platforms\AbstractPlatform; use Doctrine\DBAL\Platforms\MariaDb1027Platform; use Doctrine\DBAL\Platforms\MariaDb1052Platform; +use Doctrine\DBAL\Platforms\MariaDb1060Platform; use Doctrine\DBAL\Platforms\MariaDBPlatform; use Doctrine\DBAL\Platforms\MySQL57Platform; use Doctrine\DBAL\Platforms\MySQL80Platform; @@ -34,7 +35,8 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st MySQL80Platform::class, MariaDBPlatform::class, MariaDb1027Platform::class, - MariaDb1052Platform::class, => $this->getMySqlPlatformSQLDeclaration($column), + MariaDb1052Platform::class, + MariaDb1060Platform::class => $this->getMySqlPlatformSQLDeclaration($column), PostgreSQLPlatform::class, PostgreSQL94Platform::class, PostgreSQL100Platform::class => $this->getPostgresPlatformSQLDeclaration($column), From 7a567c41c671362c8b1a3fb55341c1237784b89c Mon Sep 17 00:00:00 2001 From: Lito Date: Fri, 26 Jan 2024 12:44:00 +0100 Subject: [PATCH 047/325] [10.x] Unified Pivot and Model Doc Block `$guarded` (#49851) If the behavior of `$guarded` in Pivot is the same as in Model, they must have the same Doc Block. --- src/Illuminate/Database/Eloquent/Relations/Pivot.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/Pivot.php b/src/Illuminate/Database/Eloquent/Relations/Pivot.php index a65ecdea6633..6e1d3f27897e 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Pivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/Pivot.php @@ -19,7 +19,7 @@ class Pivot extends Model /** * The attributes that aren't mass assignable. * - * @var array + * @var array|bool */ protected $guarded = []; } From c9de94a0146a758214b994206165814c557cc9b7 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Fri, 26 Jan 2024 16:03:45 +0100 Subject: [PATCH 048/325] [10.x] Introducing `beforeStartingTransaction` callback and use it in `LazilyRefreshDatabase` (#49853) * Introduced `beforeStartingTransaction` on `Connection` * Use new `beforeStartingTransaction ` in `LazilyRefreshDatabase` * Fix formatting * formatting --------- Co-authored-by: Taylor Otwell --- .../Database/Concerns/ManagesTransactions.php | 4 ++++ src/Illuminate/Database/Connection.php | 20 +++++++++++++++++++ .../Testing/LazilyRefreshDatabase.php | 7 +++++-- tests/Database/DatabaseConnectionTest.php | 12 +++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index 99670cf0949c..df60c61b6d87 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -119,6 +119,10 @@ protected function handleTransactionException(Throwable $e, $currentAttempt, $ma */ public function beginTransaction() { + foreach ($this->beforeStartingTransaction as $callback) { + $callback($this); + } + $this->createTransaction(); $this->transactions++; diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index a46448bb8974..f55adabc57cc 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -182,6 +182,13 @@ class Connection implements ConnectionInterface */ protected $pretending = false; + /** + * All of the callbacks that should be invoked before a transaction is started. + * + * @var \Closure[] + */ + protected $beforeStartingTransaction = []; + /** * All of the callbacks that should be invoked before a query is executed. * @@ -1021,6 +1028,19 @@ public function disconnect() $this->doctrineConnection = null; } + /** + * Register a hook to be run just before a database transaction is started. + * + * @param \Closure $callback + * @return $this + */ + public function beforeStartingTransaction(Closure $callback) + { + $this->beforeStartingTransaction[] = $callback; + + return $this; + } + /** * Register a hook to be run just before a database query is executed. * diff --git a/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php b/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php index 98204cceab48..83e4546c38d3 100644 --- a/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php @@ -17,7 +17,7 @@ public function refreshDatabase() { $database = $this->app->make('db'); - $database->beforeExecuting(function () { + $callback = function () { if (RefreshDatabaseState::$lazilyRefreshed) { return; } @@ -25,7 +25,10 @@ public function refreshDatabase() RefreshDatabaseState::$lazilyRefreshed = true; $this->baseRefreshDatabase(); - }); + }; + + $database->beforeStartingTransaction($callback); + $database->beforeExecuting($callback); $this->beforeApplicationDestroyed(function () { RefreshDatabaseState::$lazilyRefreshed = false; diff --git a/tests/Database/DatabaseConnectionTest.php b/tests/Database/DatabaseConnectionTest.php index 8709e339c226..1b6211386a04 100755 --- a/tests/Database/DatabaseConnectionTest.php +++ b/tests/Database/DatabaseConnectionTest.php @@ -484,6 +484,18 @@ public function testBeforeExecutingHooksCanBeRegistered() $connection->select('foo bar', ['baz']); } + public function testBeforeStartingTransactionHooksCanBeRegistered() + { + $this->expectException(Exception::class); + $this->expectExceptionMessage('The callback was fired'); + + $connection = $this->getMockConnection(); + $connection->beforeStartingTransaction(function () { + throw new Exception('The callback was fired'); + }); + $connection->beginTransaction(); + } + public function testPretendOnlyLogsQueries() { $connection = $this->getMockConnection(); From abc4c985d52f34a65ed1a0af8d97ad6a9a2fadd6 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Fri, 26 Jan 2024 15:04:16 +0000 Subject: [PATCH 049/325] Update facade docblocks --- src/Illuminate/Support/Facades/DB.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/DB.php b/src/Illuminate/Support/Facades/DB.php index e13bd509c9fb..a132a47aa0c8 100755 --- a/src/Illuminate/Support/Facades/DB.php +++ b/src/Illuminate/Support/Facades/DB.php @@ -52,6 +52,7 @@ * @method static float totalQueryDuration() * @method static void resetTotalQueryDuration() * @method static void reconnectIfMissingConnection() + * @method static \Illuminate\Database\Connection beforeStartingTransaction(\Closure $callback) * @method static \Illuminate\Database\Connection beforeExecuting(\Closure $callback) * @method static void listen(\Closure $callback) * @method static \Illuminate\Contracts\Database\Query\Expression raw(mixed $value) From fb9d508f62690dbbfc44150dd2831c4bc1412f83 Mon Sep 17 00:00:00 2001 From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com> Date: Fri, 26 Jan 2024 23:50:54 +0530 Subject: [PATCH 050/325] Refactor password validation rules (#49861) --- src/Illuminate/Validation/Rules/Password.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Validation/Rules/Password.php b/src/Illuminate/Validation/Rules/Password.php index 588f357334cc..3fb66e9ee2bd 100644 --- a/src/Illuminate/Validation/Rules/Password.php +++ b/src/Illuminate/Validation/Rules/Password.php @@ -312,7 +312,12 @@ public function passes($attribute, $value) $validator = Validator::make( $this->data, - [$attribute => array_merge(['string', 'min:'.$this->min], $this->customRules)], + [$attribute => [ + 'string', + 'min:'.$this->min, + ...($this->max ? ['max:'.$this->max] : []), + ...$this->customRules, + ]], $this->validator->customMessages, $this->validator->customAttributes )->after(function ($validator) use ($attribute, $value) { @@ -320,10 +325,6 @@ public function passes($attribute, $value) return; } - if ($this->max && mb_strlen($value) > $this->max) { - $validator->addFailure($attribute, 'max.string'); - } - if ($this->mixedCase && ! preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/u', $value)) { $validator->addFailure($attribute, 'password.mixed'); } From d410d61f90f4aafde972b1279abe88e113c286f5 Mon Sep 17 00:00:00 2001 From: Mateus Junges Date: Mon, 29 Jan 2024 11:21:32 -0300 Subject: [PATCH 051/325] [10.x] Fix validation message used for max file size (#49879) * Use correct validation message * Add tests * Apply styleci fixes * Update src/Illuminate/Validation/Concerns/FormatsMessages.php * Use symfony file to get attribute type * Apply fixes from styleci --------- Co-authored-by: Dries Vints --- .../Validation/Concerns/FormatsMessages.php | 17 ++++++++--------- tests/Validation/ValidationFileRuleTest.php | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Illuminate/Validation/Concerns/FormatsMessages.php b/src/Illuminate/Validation/Concerns/FormatsMessages.php index 4d4042a4e10f..2d68828d1b3a 100644 --- a/src/Illuminate/Validation/Concerns/FormatsMessages.php +++ b/src/Illuminate/Validation/Concerns/FormatsMessages.php @@ -5,6 +5,7 @@ use Closure; use Illuminate\Support\Arr; use Illuminate\Support\Str; +use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\HttpFoundation\File\UploadedFile; trait FormatsMessages @@ -218,15 +219,13 @@ protected function getAttributeType($attribute) // We assume that the attributes present in the file array are files so that // means that if the attribute does not have a numeric rule and the files // list doesn't have it we'll just consider it a string by elimination. - if ($this->hasRule($attribute, $this->numericRules)) { - return 'numeric'; - } elseif ($this->hasRule($attribute, ['Array'])) { - return 'array'; - } elseif ($this->getValue($attribute) instanceof UploadedFile) { - return 'file'; - } - - return 'string'; + return match (true) { + $this->hasRule($attribute, $this->numericRules) => 'numeric', + $this->hasRule($attribute, ['Array']) => 'array', + $this->getValue($attribute) instanceof UploadedFile, + $this->getValue($attribute) instanceof File => 'file', + default => 'string', + }; } /** diff --git a/tests/Validation/ValidationFileRuleTest.php b/tests/Validation/ValidationFileRuleTest.php index 838aa6615471..1705a3ef198b 100644 --- a/tests/Validation/ValidationFileRuleTest.php +++ b/tests/Validation/ValidationFileRuleTest.php @@ -344,6 +344,21 @@ public function testMacro() ); } + public function testItUsesTheCorrectValidationMessageForFile(): void + { + file_put_contents($path = __DIR__.'/test.json', 'this-is-a-test'); + + $file = new \Illuminate\Http\File($path); + + $this->fails( + ['max:0'], + $file, + ['validation.max.file'] + ); + + unlink($path); + } + public function testItCanSetDefaultUsing() { $this->assertInstanceOf(File::class, File::default()); From 706a7d2160d55c676a234a6c5852abf454e8621e Mon Sep 17 00:00:00 2001 From: foremtehan <53290883+foremtehan@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:56:07 +0330 Subject: [PATCH 052/325] Update README.md (#49878) * Update README.md * Update README.md --------- Co-authored-by: Taylor Otwell --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index df935e86ac3f..a6fb7790a95a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Laravel has the most extensive and thorough documentation and video tutorial lib You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. -If you're not in the mood to read, [Laracasts](https://laracasts.com) contains over 1100 video tutorials covering a range of topics including Laravel, modern PHP, unit testing, JavaScript, and more. Boost the skill level of yourself and your entire team by digging into our comprehensive video library. +If you're not in the mood to read, [Laracasts](https://laracasts.com) contains thousands of video tutorials covering a range of topics including Laravel, modern PHP, unit testing, JavaScript, and more. Boost the skill level of yourself and your entire team by digging into our comprehensive video library. ## Contributing From 4fa777d1d8b18f2b9b56e45d03c128f8591a3b99 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Mon, 29 Jan 2024 09:33:48 -0500 Subject: [PATCH 053/325] [10.x] Adds `FormRequest@getRules()` method (#49860) * adds getRules method * revert style changes * revert style changes * revert style changes * revert style changes * formatting --------- Co-authored-by: Taylor Otwell --- .../Foundation/Http/FormRequest.php | 18 +++++++--- .../Foundation/FoundationFormRequestTest.php | 34 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Foundation/Http/FormRequest.php b/src/Illuminate/Foundation/Http/FormRequest.php index 772a2635a3b4..93e7d20026e0 100644 --- a/src/Illuminate/Foundation/Http/FormRequest.php +++ b/src/Illuminate/Foundation/Http/FormRequest.php @@ -115,11 +115,11 @@ protected function getValidatorInstance() */ protected function createDefaultValidator(ValidationFactory $factory) { - $rules = method_exists($this, 'rules') ? $this->container->call([$this, 'rules']) : []; - $validator = $factory->make( - $this->validationData(), $rules, - $this->messages(), $this->attributes() + $this->validationData(), + $this->validationRules(), + $this->messages(), + $this->attributes(), )->stopOnFirstFailure($this->stopOnFirstFailure); if ($this->isPrecognitive()) { @@ -141,6 +141,16 @@ public function validationData() return $this->all(); } + /** + * Get the validation rules for this form request. + * + * @return array + */ + protected function validationRules() + { + return method_exists($this, 'rules') ? $this->container->call([$this, 'rules']) : []; + } + /** * Handle a failed validation attempt. * diff --git a/tests/Foundation/FoundationFormRequestTest.php b/tests/Foundation/FoundationFormRequestTest.php index c2d7922a5518..1573f768cfec 100644 --- a/tests/Foundation/FoundationFormRequestTest.php +++ b/tests/Foundation/FoundationFormRequestTest.php @@ -213,6 +213,22 @@ public function testRequestCanPassWithoutRulesMethod() $this->assertEquals([], $request->all()); } + public function testRequestWithGetRules() + { + FoundationTestFormRequestWithGetRules::$useRuleSet = 'a'; + $request = $this->createRequest(['a' => 1], FoundationTestFormRequestWithGetRules::class); + + $request->validateResolved(); + $this->assertEquals(['a' => 1], $request->all()); + + $this->expectException(ValidationException::class); + FoundationTestFormRequestWithGetRules::$useRuleSet = 'b'; + + $request = $this->createRequest(['a' => 1], FoundationTestFormRequestWithGetRules::class); + + $request->validateResolved(); + } + /** * Catch the given exception thrown from the executor, and return it. * @@ -482,3 +498,21 @@ public function authorize() return true; } } + +class FoundationTestFormRequestWithGetRules extends FormRequest +{ + public static $useRuleSet = 'a'; + + protected function validationRules(): array + { + if (self::$useRuleSet === 'a') { + return [ + 'a' => ['required', 'int', 'min:1'] + ]; + } else { + return [ + 'a' => ['required', 'int', 'min:2'], + ]; + } + } +} From a050fb5a80690f5623f853d0997166fbf02b4707 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 29 Jan 2024 14:34:08 +0000 Subject: [PATCH 054/325] Apply fixes from StyleCI --- tests/Foundation/FoundationFormRequestTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Foundation/FoundationFormRequestTest.php b/tests/Foundation/FoundationFormRequestTest.php index 1573f768cfec..9dd164585b5d 100644 --- a/tests/Foundation/FoundationFormRequestTest.php +++ b/tests/Foundation/FoundationFormRequestTest.php @@ -507,7 +507,7 @@ protected function validationRules(): array { if (self::$useRuleSet === 'a') { return [ - 'a' => ['required', 'int', 'min:1'] + 'a' => ['required', 'int', 'min:1'], ]; } else { return [ From 0049bc2da8424e21298bf6ccb9bc54401c847598 Mon Sep 17 00:00:00 2001 From: Eliezer Margareten <46111162+emargareten@users.noreply.github.com> Date: Mon, 29 Jan 2024 16:34:55 +0200 Subject: [PATCH 055/325] [10.x] add addGlobalScopes method (#49880) --- .../Eloquent/Concerns/HasGlobalScopes.php | 17 ++++++++++++++ .../DatabaseEloquentGlobalScopesTest.php | 23 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php b/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php index 320126366b73..7e3745f123a9 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasGlobalScopes.php @@ -33,6 +33,23 @@ public static function addGlobalScope($scope, $implementation = null) throw new InvalidArgumentException('Global scope must be an instance of Closure or Scope or be a class name of a class extending '.Scope::class); } + /** + * Register multiple global scopes on the model. + * + * @param array $scopes + * @return void + */ + public static function addGlobalScopes(array $scopes) + { + foreach ($scopes as $key => $scope) { + if (is_string($key)) { + static::addGlobalScope($key, $scope); + } else { + static::addGlobalScope($scope); + } + } + } + /** * Determine if a model has a global scope. * diff --git a/tests/Database/DatabaseEloquentGlobalScopesTest.php b/tests/Database/DatabaseEloquentGlobalScopesTest.php index a1bb15b3c4a1..bef11fbcc854 100644 --- a/tests/Database/DatabaseEloquentGlobalScopesTest.php +++ b/tests/Database/DatabaseEloquentGlobalScopesTest.php @@ -59,6 +59,14 @@ public function testClosureGlobalScopeIsApplied() $this->assertEquals([1], $query->getBindings()); } + public function testGlobalScopesCanBeRegisteredViaArray() + { + $model = new EloquentGlobalScopesArrayTestModel; + $query = $model->newQuery(); + $this->assertSame('select * from "table" where "active" = ? order by "name" asc', $query->toSql()); + $this->assertEquals([1], $query->getBindings()); + } + public function testClosureGlobalScopeCanBeRemoved() { $model = new EloquentClosureGlobalScopesTestModel; @@ -210,6 +218,21 @@ public static function boot() } } +class EloquentGlobalScopesArrayTestModel extends Model +{ + protected $table = 'table'; + + public static function boot() + { + static::addGlobalScopes([ + 'active_scope' => new ActiveScope, + fn ($query) => $query->orderBy('name'), + ]); + + parent::boot(); + } +} + class ActiveScope implements Scope { public function apply(Builder $builder, Model $model) From 334a2173a1e9fdeb861d55638f18bb54297455aa Mon Sep 17 00:00:00 2001 From: Calvin Jackson <69092766+LogicSatinn@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:41:50 +0300 Subject: [PATCH 056/325] [10.x] Allow brick/math 0.12 (#49883) * Allow brick/math version 0.12 * Allow brick/math version 0.12 in database and validation * Revert "Allow brick/math version 0.12 in database and validation" This reverts commit ef5159e070f692edb300bf9c298b198c754c9ed3. * Allow brick/math version 0.12 in database and validation --- composer.json | 2 +- src/Illuminate/Database/composer.json | 2 +- src/Illuminate/Validation/composer.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index dac49e5946c7..40255ad7b83f 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,7 @@ "ext-session": "*", "ext-tokenizer": "*", "composer-runtime-api": "^2.2", - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", "egulias/email-validator": "^3.2.1|^4.0", diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index fcc4034eee80..d807f333456e 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -17,7 +17,7 @@ "require": { "php": "^8.1", "ext-pdo": "*", - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "illuminate/collections": "^10.0", "illuminate/container": "^10.0", "illuminate/contracts": "^10.0", diff --git a/src/Illuminate/Validation/composer.json b/src/Illuminate/Validation/composer.json index 611f05092f38..73670e87f98e 100755 --- a/src/Illuminate/Validation/composer.json +++ b/src/Illuminate/Validation/composer.json @@ -17,7 +17,7 @@ "php": "^8.1", "ext-filter": "*", "ext-mbstring": "*", - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "egulias/email-validator": "^3.2.5|^4.0", "illuminate/collections": "^10.0", "illuminate/container": "^10.0", From e9c67dbd1b98def11ff6c2be843499d5d828af40 Mon Sep 17 00:00:00 2001 From: Peter Elmered Date: Mon, 29 Jan 2024 15:55:52 +0100 Subject: [PATCH 057/325] [10.x] Add support for streamed JSON Response (#49873) * Add support for streamed JSON Response * Add test helper and tests for streamed JSON Responses * Remove unused imports * Remove unused method * Remove unused imports * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Routing/ResponseFactory.php | 15 ++++++++ src/Illuminate/Testing/TestResponse.php | 15 +++++++- tests/Testing/TestResponseTest.php | 44 ++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Routing/ResponseFactory.php b/src/Illuminate/Routing/ResponseFactory.php index 35b209584549..84c69225e370 100644 --- a/src/Illuminate/Routing/ResponseFactory.php +++ b/src/Illuminate/Routing/ResponseFactory.php @@ -10,6 +10,7 @@ use Illuminate\Support\Str; use Illuminate\Support\Traits\Macroable; use Symfony\Component\HttpFoundation\BinaryFileResponse; +use Symfony\Component\HttpFoundation\StreamedJsonResponse; use Symfony\Component\HttpFoundation\StreamedResponse; use Throwable; @@ -129,6 +130,20 @@ public function stream($callback, $status = 200, array $headers = []) return new StreamedResponse($callback, $status, $headers); } + /** + * Create a new streamed response instance. + * + * @param array $data + * @param int $status + * @param array $headers + * @param int $encodingOptions + * @return \Symfony\Component\HttpFoundation\StreamedJsonResponse + */ + public function streamJson($data, $status = 200, $headers = [], $encodingOptions = JsonResponse::DEFAULT_ENCODING_OPTIONS) + { + return new StreamedJsonResponse($data, $status, $headers, $encodingOptions); + } + /** * Create a new streamed response instance as a file download. * diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index dc9b405c0565..ea19db479a03 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -26,6 +26,7 @@ use PHPUnit\Framework\ExpectationFailedException; use ReflectionProperty; use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\StreamedJsonResponse; use Symfony\Component\HttpFoundation\StreamedResponse; /** @@ -531,6 +532,17 @@ public function assertStreamedContent($value) return $this; } + /** + * Assert that the given array matches the streamed JSON response content. + * + * @param array $value + * @return $this + */ + public function assertStreamedJsonContent($value) + { + return $this->assertStreamedContent(json_encode($value, JSON_THROW_ON_ERROR)); + } + /** * Assert that the given string or array of strings are contained within the response. * @@ -1564,7 +1576,8 @@ public function streamedContent() return $this->streamedContent; } - if (! $this->baseResponse instanceof StreamedResponse) { + if (! $this->baseResponse instanceof StreamedResponse + && ! $this->baseResponse instanceof StreamedJsonResponse) { PHPUnit::fail('The response is not a streamed response.'); } diff --git a/tests/Testing/TestResponseTest.php b/tests/Testing/TestResponseTest.php index 3934bbbebc13..f4c1ee713a6b 100644 --- a/tests/Testing/TestResponseTest.php +++ b/tests/Testing/TestResponseTest.php @@ -28,6 +28,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\BinaryFileResponse; use Symfony\Component\HttpFoundation\Cookie; +use Symfony\Component\HttpFoundation\StreamedJsonResponse; use Symfony\Component\HttpFoundation\StreamedResponse; class TestResponseTest extends TestCase @@ -285,6 +286,49 @@ public function testAssertStreamedContent() } } + public function testAssertStreamedJsonContent() + { + $response = TestResponse::fromBaseResponse( + new StreamedJsonResponse([ + 'data' => $this->yieldTestModels(), + ]) + ); + + $response->assertStreamedJsonContent([ + 'data' => [ + ['id' => 1], + ['id' => 2], + ['id' => 3], + ], + ]); + + try { + $response->assertStreamedJsonContent([ + 'data' => [ + ['id' => 1], + ['id' => 2], + ], + ]); + $this->fail('xxxx'); + } catch (AssertionFailedError $e) { + $this->assertSame('Failed asserting that two strings are identical.', $e->getMessage()); + } + + try { + $response->assertStreamedContent('not expected response string'); + $this->fail('xxxx'); + } catch (AssertionFailedError $e) { + $this->assertSame('Failed asserting that two strings are identical.', $e->getMessage()); + } + } + + public function yieldTestModels() + { + yield new TestModel(['id' => 1]); + yield new TestModel(['id' => 2]); + yield new TestModel(['id' => 3]); + } + public function testAssertSee() { $response = $this->makeMockResponse([ From 784d076340d58fd584b1602955e9474a4834a8ca Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Mon, 29 Jan 2024 14:56:21 +0000 Subject: [PATCH 058/325] Update facade docblocks --- src/Illuminate/Support/Facades/Response.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/Response.php b/src/Illuminate/Support/Facades/Response.php index b92259ddeb76..a5addbfaa6bf 100755 --- a/src/Illuminate/Support/Facades/Response.php +++ b/src/Illuminate/Support/Facades/Response.php @@ -11,6 +11,7 @@ * @method static \Illuminate\Http\JsonResponse json(mixed $data = [], int $status = 200, array $headers = [], int $options = 0) * @method static \Illuminate\Http\JsonResponse jsonp(string $callback, mixed $data = [], int $status = 200, array $headers = [], int $options = 0) * @method static \Symfony\Component\HttpFoundation\StreamedResponse stream(callable $callback, int $status = 200, array $headers = []) + * @method static \Symfony\Component\HttpFoundation\StreamedJsonResponse streamJson(array $data, int $status = 200, array $headers = [], int $encodingOptions = 15) * @method static \Symfony\Component\HttpFoundation\StreamedResponse streamDownload(callable $callback, string|null $name = null, array $headers = [], string|null $disposition = 'attachment') * @method static \Symfony\Component\HttpFoundation\BinaryFileResponse download(\SplFileInfo|string $file, string|null $name = null, array $headers = [], string|null $disposition = 'attachment') * @method static \Symfony\Component\HttpFoundation\BinaryFileResponse file(\SplFileInfo|string $file, array $headers = []) From af2a7caa70ebe2d50c3aff8c222222e30cd83d12 Mon Sep 17 00:00:00 2001 From: Lito Date: Tue, 30 Jan 2024 04:11:34 +0100 Subject: [PATCH 059/325] [10.x] Using the native fopen exception in LockableFile.php (#49895) * [10.x] Using the native fopen exception in LockableFile.php * Removed unused Exception. --- src/Illuminate/Filesystem/LockableFile.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Illuminate/Filesystem/LockableFile.php b/src/Illuminate/Filesystem/LockableFile.php index 8b2de765eaad..d354b884036a 100644 --- a/src/Illuminate/Filesystem/LockableFile.php +++ b/src/Illuminate/Filesystem/LockableFile.php @@ -2,7 +2,6 @@ namespace Illuminate\Filesystem; -use Exception; use Illuminate\Contracts\Filesystem\LockTimeoutException; class LockableFile @@ -67,11 +66,7 @@ protected function ensureDirectoryExists($path) */ protected function createResource($path, $mode) { - $this->handle = @fopen($path, $mode); - - if (! $this->handle) { - throw new Exception('Unable to create lockable file: '.$path.'. Please ensure you have permission to create files in this location.'); - } + $this->handle = fopen($path, $mode); } /** From a4dfd9f5c70c8d7d1f2979640f2fbc3b6af94b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=2E=20Nagy=20Gerg=C5=91?= Date: Tue, 30 Jan 2024 16:45:44 +0100 Subject: [PATCH 060/325] [10.x] Fix LazilyRefreshDatabase when testing artisan commands (#49914) --- src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php b/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php index 83e4546c38d3..3de593d9406b 100644 --- a/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php +++ b/src/Illuminate/Foundation/Testing/LazilyRefreshDatabase.php @@ -24,7 +24,13 @@ public function refreshDatabase() RefreshDatabaseState::$lazilyRefreshed = true; + $shouldMockOutput = $this->mockConsoleOutput; + + $this->mockConsoleOutput = false; + $this->baseRefreshDatabase(); + + $this->mockConsoleOutput = $shouldMockOutput; }; $database->beforeStartingTransaction($callback); From b1ebf4f8993e05323b195936004f60f3a998d054 Mon Sep 17 00:00:00 2001 From: Tobias Petry Date: Tue, 30 Jan 2024 16:46:52 +0100 Subject: [PATCH 061/325] [10.x] Fix expressions in with-functions doing aggregates (#49912) * support expression in with-functions doing aggregates * apply Laravel docblock rules * typo --- .../Concerns/QueriesRelationships.php | 28 ++++++----- ...baseEloquentBelongsToManyAggregateTest.php | 12 +++++ .../Database/DatabaseEloquentBuilderTest.php | 46 +++++++++++++++++++ 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index 154717fa4d81..d9608f2f6c21 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -599,7 +599,7 @@ public function orWhereBelongsTo($related, $relationshipName = null) * Add subselect queries to include an aggregate value for a relationship. * * @param mixed $relations - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $function * @return $this */ @@ -630,15 +630,19 @@ public function withAggregate($relations, $column, $function = null) $relation = $this->getRelationWithoutConstraints($name); if ($function) { - $hashedColumn = $this->getRelationHashedColumn($column, $relation); + if ($this->getGrammar()->isExpression($column)) { + $aggregateColumn = $this->getGrammar()->getValue($column); + } else { + $hashedColumn = $this->getRelationHashedColumn($column, $relation); - $wrappedColumn = $this->getQuery()->getGrammar()->wrap( - $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn) - ); + $aggregateColumn = $this->getQuery()->getGrammar()->wrap( + $column === '*' ? $column : $relation->getRelated()->qualifyColumn($hashedColumn) + ); + } - $expression = $function === 'exists' ? $wrappedColumn : sprintf('%s(%s)', $function, $wrappedColumn); + $expression = $function === 'exists' ? $aggregateColumn : sprintf('%s(%s)', $function, $aggregateColumn); } else { - $expression = $column; + $expression = $this->getGrammar()->getValue($column); } // Here, we will grab the relationship sub-query and prepare to add it to the main query @@ -667,7 +671,7 @@ public function withAggregate($relations, $column, $function = null) // the query builder. Then, we will return the builder instance back to the developer // for further constraint chaining that needs to take place on the query as needed. $alias ??= Str::snake( - preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function $column") + preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function {$this->getGrammar()->getValue($column)}") ); if ($function === 'exists') { @@ -719,7 +723,7 @@ public function withCount($relations) * Add subselect queries to include the max of the relation's column. * * @param string|array $relation - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function withMax($relation, $column) @@ -731,7 +735,7 @@ public function withMax($relation, $column) * Add subselect queries to include the min of the relation's column. * * @param string|array $relation - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function withMin($relation, $column) @@ -743,7 +747,7 @@ public function withMin($relation, $column) * Add subselect queries to include the sum of the relation's column. * * @param string|array $relation - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function withSum($relation, $column) @@ -755,7 +759,7 @@ public function withSum($relation, $column) * Add subselect queries to include the average of the relation's column. * * @param string|array $relation - * @param string $column + * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @return $this */ public function withAvg($relation, $column) diff --git a/tests/Database/DatabaseEloquentBelongsToManyAggregateTest.php b/tests/Database/DatabaseEloquentBelongsToManyAggregateTest.php index 04db5bba5e7d..7847787984a3 100644 --- a/tests/Database/DatabaseEloquentBelongsToManyAggregateTest.php +++ b/tests/Database/DatabaseEloquentBelongsToManyAggregateTest.php @@ -4,6 +4,7 @@ use Illuminate\Database\Capsule\Manager as DB; use Illuminate\Database\Eloquent\Model as Eloquent; +use Illuminate\Database\Query\Expression; use PHPUnit\Framework\TestCase; class DatabaseEloquentBelongsToManyAggregateTest extends TestCase @@ -45,6 +46,17 @@ public function testWithSumSameTable() $this->assertEquals(1200, $order->total_allocated); } + public function testWithSumExpression() + { + $this->seedData(); + + $order = BelongsToManyAggregateTestTestTransaction::query() + ->withSum('allocatedTo as total_allocated', new Expression('allocations.amount * 2')) + ->first(); + + $this->assertEquals(2400, $order->total_allocated); + } + /** * Setup the database schema. * diff --git a/tests/Database/DatabaseEloquentBuilderTest.php b/tests/Database/DatabaseEloquentBuilderTest.php index 48e3569738b8..a2b13bd867cf 100755 --- a/tests/Database/DatabaseEloquentBuilderTest.php +++ b/tests/Database/DatabaseEloquentBuilderTest.php @@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Query\Builder as BaseBuilder; +use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\Grammars\Grammar; use Illuminate\Database\Query\Processors\Processor; use Illuminate\Support\Carbon; @@ -1278,6 +1279,15 @@ public function testWithMin() $this->assertSame('select "eloquent_builder_test_model_parent_stubs".*, (select min("eloquent_builder_test_model_close_related_stubs"."price") from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_min_price" from "eloquent_builder_test_model_parent_stubs"', $builder->toSql()); } + public function testWithMinExpression() + { + $model = new EloquentBuilderTestModelParentStub; + + $builder = $model->withMin('foo', new Expression('price - discount')); + + $this->assertSame('select "eloquent_builder_test_model_parent_stubs".*, (select min(price - discount) from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_min_price_discount" from "eloquent_builder_test_model_parent_stubs"', $builder->toSql()); + } + public function testWithMinOnBelongsToMany() { $model = new EloquentBuilderTestModelParentStub; @@ -1302,6 +1312,42 @@ public function testWithMinOnSelfRelated() $this->assertSame('select "self_related_stubs".*, (select min("self_alias_hash"."created_at") from "self_related_stubs" as "self_alias_hash" where "self_related_stubs"."id" = "self_alias_hash"."parent_id") as "child_foos_min_created_at" from "self_related_stubs"', $sql); } + public function testWithMax() + { + $model = new EloquentBuilderTestModelParentStub; + + $builder = $model->withMax('foo', 'price'); + + $this->assertSame('select "eloquent_builder_test_model_parent_stubs".*, (select max("eloquent_builder_test_model_close_related_stubs"."price") from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_max_price" from "eloquent_builder_test_model_parent_stubs"', $builder->toSql()); + } + + public function testWithMaxExpression() + { + $model = new EloquentBuilderTestModelParentStub; + + $builder = $model->withMax('foo', new Expression('price - discount')); + + $this->assertSame('select "eloquent_builder_test_model_parent_stubs".*, (select max(price - discount) from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_max_price_discount" from "eloquent_builder_test_model_parent_stubs"', $builder->toSql()); + } + + public function testWithAvg() + { + $model = new EloquentBuilderTestModelParentStub; + + $builder = $model->withAvg('foo', 'price'); + + $this->assertSame('select "eloquent_builder_test_model_parent_stubs".*, (select avg("eloquent_builder_test_model_close_related_stubs"."price") from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_avg_price" from "eloquent_builder_test_model_parent_stubs"', $builder->toSql()); + } + + public function testWitAvgExpression() + { + $model = new EloquentBuilderTestModelParentStub; + + $builder = $model->withAvg('foo', new Expression('price - discount')); + + $this->assertSame('select "eloquent_builder_test_model_parent_stubs".*, (select avg(price - discount) from "eloquent_builder_test_model_close_related_stubs" where "eloquent_builder_test_model_parent_stubs"."foo_id" = "eloquent_builder_test_model_close_related_stubs"."id") as "foo_avg_price_discount" from "eloquent_builder_test_model_parent_stubs"', $builder->toSql()); + } + public function testWithCountAndConstraintsAndHaving() { $model = new EloquentBuilderTestModelParentStub; From fab8b136938611449d9f5bc40359cac8d4bf1e90 Mon Sep 17 00:00:00 2001 From: jagers <7496717+jagers@users.noreply.github.com> Date: Tue, 30 Jan 2024 07:48:38 -0800 Subject: [PATCH 062/325] Fix redis tag cache ttls in past no getting flushed (#49864) Co-authored-by: Jack Gersten --- src/Illuminate/Cache/RedisTagSet.php | 6 +++--- src/Illuminate/Cache/RedisTaggedCache.php | 20 ++++++++++++++++++-- tests/Integration/Cache/RedisStoreTest.php | 22 ++++++++++++++++++++++ 3 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Cache/RedisTagSet.php b/src/Illuminate/Cache/RedisTagSet.php index bf4c53869361..b5fd0e2593bc 100644 --- a/src/Illuminate/Cache/RedisTagSet.php +++ b/src/Illuminate/Cache/RedisTagSet.php @@ -11,13 +11,13 @@ class RedisTagSet extends TagSet * Add a reference entry to the tag set's underlying sorted set. * * @param string $key - * @param int $ttl + * @param int|null $ttl * @param string $updateWhen * @return void */ - public function addEntry(string $key, int $ttl = 0, $updateWhen = null) + public function addEntry(string $key, int $ttl = null, $updateWhen = null) { - $ttl = $ttl > 0 ? Carbon::now()->addSeconds($ttl)->getTimestamp() : -1; + $ttl = is_null($ttl) ? -1 : Carbon::now()->addSeconds($ttl)->getTimestamp(); foreach ($this->tagIds() as $tagKey) { if ($updateWhen) { diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index b8120be95c03..75c1001ce747 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -14,9 +14,19 @@ class RedisTaggedCache extends TaggedCache */ public function add($key, $value, $ttl = null) { + $seconds = null; + + if ($ttl !== null) { + $seconds = $this->getSeconds($ttl); + + if ($seconds <= 0) { + return false; + } + } + $this->tags->addEntry( $this->itemKey($key), - ! is_null($ttl) ? $this->getSeconds($ttl) : 0 + $seconds ); return parent::add($key, $value, $ttl); @@ -36,9 +46,15 @@ public function put($key, $value, $ttl = null) return $this->forever($key, $value); } + $seconds = $this->getSeconds($ttl); + + if ($seconds <= 0) { + return false; + } + $this->tags->addEntry( $this->itemKey($key), - $this->getSeconds($ttl) + $seconds ); return parent::put($key, $value, $ttl); diff --git a/tests/Integration/Cache/RedisStoreTest.php b/tests/Integration/Cache/RedisStoreTest.php index c8c7abb13f62..dd0a1943b246 100644 --- a/tests/Integration/Cache/RedisStoreTest.php +++ b/tests/Integration/Cache/RedisStoreTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Integration\Cache; +use DateTime; use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Redis; @@ -139,6 +140,27 @@ public function testIncrementedTagEntriesProperlyTurnStale() $this->assertEquals(0, count($keyCount)); } + public function testPastTtlTagEntriesAreNotAdded() + { + Cache::store('redis')->clear(); + + Cache::store('redis')->tags(['votes'])->add('person-1', 0, new DateTime('yesterday')); + + $keyCount = Cache::store('redis')->connection()->keys('*'); + $this->assertEquals(0, count($keyCount)); + } + + public function testPutPastTtlTagEntriesProperlyTurnStale() + { + Cache::store('redis')->clear(); + + Cache::store('redis')->tags(['votes'])->put('person-1', 0, new DateTime('yesterday')); + Cache::store('redis')->tags(['votes'])->flushStale(); + + $keyCount = Cache::store('redis')->connection()->keys('*'); + $this->assertEquals(0, count($keyCount)); + } + public function testTagsCanBeFlushedBySingleKey() { Cache::store('redis')->clear(); From 85f06e8b418dbd7eddb43d990b663ca455dfb071 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Jan 2024 16:52:08 +0100 Subject: [PATCH 063/325] wip --- .github/workflows/releases.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 62a97f54b6c8..6b34532d5121 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -22,6 +22,8 @@ jobs: - name: Update Application.php version run: | sed -i "s/const VERSION = '.*';/const VERSION = '${{ steps.version.outputs.version }}';/g" src/Illuminate/Foundation/Application.php + git config --global user.name 'Taylor Otwell' + git config --global user.email 'taylorotwell@users.noreply.github.com' git add src/Illuminate/Foundation/Application.php git commit -m "Update version to v${{ steps.version.outputs.version }}" git push origin ${{ github.ref_name }} From 41bd2c15386b80ecc1f8da8419d95dcd18adc49e Mon Sep 17 00:00:00 2001 From: Victor Schedlyn GUTT <39224143+VicGUTT@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:55:48 +0100 Subject: [PATCH 064/325] [10.x] Fix - The `Translator` may incorrectly report the locale of a missing translation key (#49900) * fix: "Translator" returns correct locale for missing keys * chore: variable rename --- src/Illuminate/Translation/Translator.php | 4 ++-- tests/Integration/Translation/TranslatorTest.php | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Translation/Translator.php b/src/Illuminate/Translation/Translator.php index 634a102d54c7..7b9ab9a56d6c 100755 --- a/src/Illuminate/Translation/Translator.php +++ b/src/Illuminate/Translation/Translator.php @@ -160,9 +160,9 @@ public function get($key, array $replace = [], $locale = null, $fallback = true) // the translator was instantiated. Then, we can load the lines and return. $locales = $fallback ? $this->localeArray($locale) : [$locale]; - foreach ($locales as $locale) { + foreach ($locales as $languageLineLocale) { if (! is_null($line = $this->getLine( - $namespace, $group, $locale, $item, $replace + $namespace, $group, $languageLineLocale, $item, $replace ))) { return $line; } diff --git a/tests/Integration/Translation/TranslatorTest.php b/tests/Integration/Translation/TranslatorTest.php index 5e374e82d1cb..de82aabce403 100644 --- a/tests/Integration/Translation/TranslatorTest.php +++ b/tests/Integration/Translation/TranslatorTest.php @@ -70,4 +70,17 @@ public function testItCanHandleMissingKeysNoReturn() $this->app['translator']->handleMissingKeysUsing(null); } + + public function testItReturnsCorrectLocaleForMissingKeys() + { + $this->app['translator']->handleMissingKeysUsing(function ($key, $replacements, $locale) { + $_SERVER['__missing_translation_key_locale'] = $locale; + }); + + $this->app['translator']->get('some missing key', [], 'ht'); + + $this->assertSame('ht', $_SERVER['__missing_translation_key_locale']); + + $this->app['translator']->handleMissingKeysUsing(null); + } } From fc87e13c8e42190488539f084724452a9aa43473 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 30 Jan 2024 15:56:20 +0000 Subject: [PATCH 065/325] Update version to v --- 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 34a5c99f1fc2..882e3a2a91a7 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.42.0'; + const VERSION = ''; /** * The base path for the Laravel installation. From d48d28ea9a20de090b7fa42faddc1880a4864883 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Jan 2024 16:57:28 +0100 Subject: [PATCH 066/325] Update Application.php --- 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 882e3a2a91a7..34a5c99f1fc2 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = ''; + const VERSION = '10.42.0'; /** * The base path for the Laravel installation. From cc653b080465072df501a313171acc8fc9615cad Mon Sep 17 00:00:00 2001 From: Punyapal Shah <53343069+MrPunyapal@users.noreply.github.com> Date: Tue, 30 Jan 2024 21:34:33 +0530 Subject: [PATCH 067/325] [10.x] fix Before/After validation rules (#49871) * added checks for 2nd date in compareDates * Add validation tests for date comparison * formatting --------- Co-authored-by: Taylor Otwell --- .../Validation/Concerns/ValidatesAttributes.php | 8 +++++++- tests/Validation/ValidationValidatorTest.php | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index b4335fe90059..c95a9f4f9999 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -253,7 +253,13 @@ protected function compareDates($attribute, $value, $parameters, $operator) } if (is_null($date = $this->getDateTimestamp($parameters[0]))) { - $date = $this->getDateTimestamp($this->getValue($parameters[0])); + $comparedValue = $this->getValue($parameters[0]); + + if (! is_string($comparedValue) && ! is_numeric($comparedValue) && ! $comparedValue instanceof DateTimeInterface) { + return false; + } + + $date = $this->getDateTimestamp($comparedValue); } return $this->compare($this->getDateTimestamp($value), $date, $operator); diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 7c9d7b31d3a2..e351c118eb30 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -5980,6 +5980,12 @@ public function testBeforeAndAfter() $v = new Validator($trans, ['x' => '0001-01-01T00:00'], ['x' => 'after:1970-01-01']); $this->assertTrue($v->fails()); + + $v = new Validator($trans, ['x' => ['a' => ['v' => 'c']], 'y' => 'invalid'], ['x' => 'date', 'y' => 'date|after:x']); + $this->assertTrue($v->fails()); + + $v = new Validator($trans, ['x' => ['a' => ['v' => 'c']], 'y' => 'invalid'], ['x' => 'date', 'y' => 'date|before:x']); + $this->assertTrue($v->fails()); } public function testBeforeAndAfterWithFormat() From 25c4323227e2248a7be5e8bc2dcc74d59015f48c Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Jan 2024 17:06:29 +0100 Subject: [PATCH 068/325] wip --- .github/workflows/releases.yml | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 6b34532d5121..26fbf1c9f436 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -17,7 +17,9 @@ jobs: - name: Remove optional "v" prefix id: version - run: VERSION=${{ inputs.version }} echo "::set-output name=version::${VERSION#v}" + run: | + VERSION=${{ inputs.version }} + echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT" - name: Update Application.php version run: | @@ -34,15 +36,11 @@ jobs: host: 104.248.56.26 username: forge key: ${{ secrets.SSH_PRIVATE_KEY_SPLITTER }} - - - name: Change directory - run: cd laravel-${{ github.ref_name }} - - - name: Pull in latest changes - run: git pull origin ${{ github.ref_name }} - - - name: Run release script - run: bash bin/release v${{ steps.version.outputs.version }} + script: | + cd laravel-${{ github.ref_name }} + git pull origin ${{ github.ref_name }} + bash bin/release v${{ steps.version.outputs.version }} + script_stop: true - name: Generate release notes id: notes From 041b733ff662a42c29d4d0b48307cd3d9f4127a6 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 30 Jan 2024 16:07:50 +0000 Subject: [PATCH 069/325] Update version to v10.43.0 --- 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 34a5c99f1fc2..c1746bfab919 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.42.0'; + const VERSION = '10.43.0'; /** * The base path for the Laravel installation. From 95b6db5b3e267fa6ed485d5fbf7a888e2b3ee0d9 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Jan 2024 17:09:01 +0100 Subject: [PATCH 070/325] wip --- .github/workflows/releases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 26fbf1c9f436..a7d452493d67 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -39,7 +39,7 @@ jobs: script: | cd laravel-${{ github.ref_name }} git pull origin ${{ github.ref_name }} - bash bin/release v${{ steps.version.outputs.version }} + bash ./bin/release v${{ steps.version.outputs.version }} script_stop: true - name: Generate release notes From a2475b17e480e53ac281d0687735fe4e09dd8681 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Jan 2024 17:18:13 +0100 Subject: [PATCH 071/325] wip --- .github/workflows/releases.yml | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index a7d452493d67..e4d56e9a9b7e 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -22,13 +22,15 @@ jobs: echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT" - name: Update Application.php version - run: | - sed -i "s/const VERSION = '.*';/const VERSION = '${{ steps.version.outputs.version }}';/g" src/Illuminate/Foundation/Application.php - git config --global user.name 'Taylor Otwell' - git config --global user.email 'taylorotwell@users.noreply.github.com' - git add src/Illuminate/Foundation/Application.php - git commit -m "Update version to v${{ steps.version.outputs.version }}" - git push origin ${{ github.ref_name }} + run: sed -i "s/const VERSION = '.*';/const VERSION = '${{ steps.version.outputs.version }}';/g" src/Illuminate/Foundation/Application.php + + - name: Commit version change + uses: EndBug/add-and-commit@v9 + with: + add: src/Illuminate/Foundation/Application.php + default_author: github_actions + message: "Update version to v${{ steps.version.outputs.version }}" + push: origin ${{ github.ref_name }} - name: SSH into splitter server uses: appleboy/ssh-action@master From 5a82302b063296f981cd1e77877f931712cf06a3 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Jan 2024 17:21:10 +0100 Subject: [PATCH 072/325] wip --- .github/workflows/releases.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index e4d56e9a9b7e..80defda46f2e 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -25,12 +25,9 @@ jobs: run: sed -i "s/const VERSION = '.*';/const VERSION = '${{ steps.version.outputs.version }}';/g" src/Illuminate/Foundation/Application.php - name: Commit version change - uses: EndBug/add-and-commit@v9 + uses: stefanzweifel/git-auto-commit-action@v5 with: - add: src/Illuminate/Foundation/Application.php - default_author: github_actions - message: "Update version to v${{ steps.version.outputs.version }}" - push: origin ${{ github.ref_name }} + commit_message: "Update version to v${{ steps.version.outputs.version }}" - name: SSH into splitter server uses: appleboy/ssh-action@master From 4f7802dfc9993cb57cf69615491ce1a7eb2e9529 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Jan 2024 17:25:02 +0100 Subject: [PATCH 073/325] wip --- .github/workflows/releases.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 80defda46f2e..d5d873b08e85 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -38,7 +38,7 @@ jobs: script: | cd laravel-${{ github.ref_name }} git pull origin ${{ github.ref_name }} - bash ./bin/release v${{ steps.version.outputs.version }} + bash ./bin/release.sh v${{ steps.version.outputs.version }} script_stop: true - name: Generate release notes From d65ea42dbec9137b94c2793b5fe06e75e8998764 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 30 Jan 2024 16:34:59 +0000 Subject: [PATCH 074/325] Update CHANGELOG --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc51589166da..8eb175d5933c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,35 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.42.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.43.0...10.x) + +## [v10.43.0](https://github.com/laravel/framework/compare/v10.42.0...v10.43.0) - 2024-01-30 + +* [10.x] Add storage:unlink command by [@salkovmx](https://github.com/salkovmx) in https://github.com/laravel/framework/pull/49795 +* [10.x] Unify `\Illuminate\Log\LogManager` method definition comments with `\Psr\Logger\Interface` by [@eusonlito](https://github.com/eusonlito) in https://github.com/laravel/framework/pull/49805 +* [10.x] class-name string argument for global scopes by [@emargareten](https://github.com/emargareten) in https://github.com/laravel/framework/pull/49802 +* [10.x] Add `hasIndex()` and minor Schema enhancements by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/49796 +* [10.x] Do not touch `BelongsToMany` relation when using `withoutTouching` by [@mateusjunges](https://github.com/mateusjunges) in https://github.com/laravel/framework/pull/49798 +* [10.x] Check properties on mailables are initialized before sharing with the view by [@j3j5](https://github.com/j3j5) in https://github.com/laravel/framework/pull/49813 +* [10.x] Remove duplicate actions/checkout from queue workflow by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/49828 +* [10.x] Add `insertOrIgnoreUsing` for Eloquent by [@trovster](https://github.com/trovster) in https://github.com/laravel/framework/pull/49827 +* [10.x] Make `hasIndex()` Order-sensitive by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/49840 +* [10.x] Release action by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/49838 +* [10.x] Add MariaDb1060Platform by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/49848 +* [10.x] Unified Pivot and Model Doc Block `$guarded` by [@eusonlito](https://github.com/eusonlito) in https://github.com/laravel/framework/pull/49851 +* [10.x] Introducing `beforeStartingTransaction` callback and use it in `LazilyRefreshDatabase` by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/laravel/framework/pull/49853 +* [10.x] fix password max validation message by [@MrPunyapal](https://github.com/MrPunyapal) in https://github.com/laravel/framework/pull/49861 +* [10.x] Fix validation message used for max file size by [@mateusjunges](https://github.com/mateusjunges) in https://github.com/laravel/framework/pull/49879 +* Update README.md by [@foremtehan](https://github.com/foremtehan) in https://github.com/laravel/framework/pull/49878 +* [10.x] Adds `FormRequest[@getRules](https://github.com/getRules)()` method by [@cosmastech](https://github.com/cosmastech) in https://github.com/laravel/framework/pull/49860 +* [10.x] add addGlobalScopes method by [@emargareten](https://github.com/emargareten) in https://github.com/laravel/framework/pull/49880 +* [10.x] Allow brick/math 0.12 by [@LogicSatinn](https://github.com/LogicSatinn) in https://github.com/laravel/framework/pull/49883 +* [10.x] Add support for streamed JSON Response by [@pelmered](https://github.com/pelmered) in https://github.com/laravel/framework/pull/49873 +* [10.x] Using the native fopen exception in LockableFile.php by [@eusonlito](https://github.com/eusonlito) in https://github.com/laravel/framework/pull/49895 +* [10.x] Fix LazilyRefreshDatabase when testing artisan commands by [@iamgergo](https://github.com/iamgergo) in https://github.com/laravel/framework/pull/49914 +* [10.x] Fix expressions in with-functions doing aggregates by [@tpetry](https://github.com/tpetry) in https://github.com/laravel/framework/pull/49912 +* [10.x] Fix redis tag entries never becoming stale if cache ttl is past time by [@jagers](https://github.com/jagers) in https://github.com/laravel/framework/pull/49864 +* [10.x] Fix - The `Translator` may incorrectly report the locale of a missing translation key by [@VicGUTT](https://github.com/VicGUTT) in https://github.com/laravel/framework/pull/49900 +* [10.x] fix Before/After validation rules by [@MrPunyapal](https://github.com/MrPunyapal) in https://github.com/laravel/framework/pull/49871 ## [v10.42.0](https://github.com/laravel/framework/compare/v10.41.0...v10.42.0) - 2024-01-23 From 8206d96832524c0fc69f4d29bfd6e99dbebc4c80 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Wed, 31 Jan 2024 18:04:49 +0100 Subject: [PATCH 075/325] Fix empty request for HTTP connection exception (#49924) --- src/Illuminate/Http/Client/PendingRequest.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 28defb4d9607..95d48e0f56bc 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -921,7 +921,7 @@ public function send(string $method, string $url, array $options = []) } }); } catch (ConnectException $e) { - $this->dispatchConnectionFailedEvent(); + $this->dispatchConnectionFailedEvent(new Request($e->getRequest())); throw new ConnectionException($e->getMessage(), 0, $e); } @@ -1010,7 +1010,7 @@ protected function makePromise(string $method, string $url, array $options = []) }) ->otherwise(function (OutOfBoundsException|TransferException $e) { if ($e instanceof ConnectException) { - $this->dispatchConnectionFailedEvent(); + $this->dispatchConnectionFailedEvent(new Request($e->getRequest())); } return $e instanceof RequestException && $e->hasResponse() ? $this->populateResponse($this->newResponse($e->getResponse())) : $e; @@ -1405,8 +1405,7 @@ protected function dispatchRequestSendingEvent() */ protected function dispatchResponseReceivedEvent(Response $response) { - if (! ($dispatcher = $this->factory?->getDispatcher()) || - ! $this->request) { + if (! ($dispatcher = $this->factory?->getDispatcher()) || ! $this->request) { return; } @@ -1416,12 +1415,13 @@ protected function dispatchResponseReceivedEvent(Response $response) /** * Dispatch the ConnectionFailed event if a dispatcher is available. * + * @param \Illuminate\Http\Client\Request $request * @return void */ - protected function dispatchConnectionFailedEvent() + protected function dispatchConnectionFailedEvent(Request $request) { if ($dispatcher = $this->factory?->getDispatcher()) { - $dispatcher->dispatch(new ConnectionFailed($this->request)); + $dispatcher->dispatch(new ConnectionFailed($request)); } } From 4d4a76031a8a0f6e33439ffc4edbaadd67970ea6 Mon Sep 17 00:00:00 2001 From: Craig Morris Date: Thu, 1 Feb 2024 10:23:24 +1100 Subject: [PATCH 076/325] [10.x] Add Collection::select() method (#49845) * Add select method to Collection * style * Update Collection.php * Update LazyCollection.php --------- Co-authored-by: Craig Morris <> Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/Arr.php | 14 +++++++ src/Illuminate/Collections/Collection.php | 21 ++++++++++ src/Illuminate/Collections/LazyCollection.php | 41 +++++++++++++++++++ tests/Support/SupportCollectionTest.php | 31 ++++++++++++++ 4 files changed, 107 insertions(+) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index d1e4a40ae686..482bbadf0fb0 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -491,6 +491,20 @@ public static function only($array, $keys) return array_intersect_key($array, array_flip((array) $keys)); } + /** + * Select an array of values from an array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function select($array, $keys) + { + return static::map($array, function ($item) use ($keys) { + return array_intersect_key($item, array_flip((array) $keys)); + }); + } + /** * Pluck an array of values from an array. * diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 7869e1ce65e0..6a2599946e7c 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -918,6 +918,27 @@ public function only($keys) return new static(Arr::only($this->items, $keys)); } + /** + * Select specific values from the items within the collection. + * + * @param \Illuminate\Support\Enumerable|array|string|null $keys + * @return static + */ + public function select($keys) + { + if (is_null($keys)) { + return new static($this->items); + } + + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + return new static(Arr::select($this->items, $keys)); + } + /** * Get and remove the last N items from the collection. * diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index e741ba0bf0a0..b3fbb75c0c3d 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -953,6 +953,47 @@ public function only($keys) }); } + /** + * Select specific values from the items within the collection. + * + * @param \Illuminate\Support\Enumerable|array|string $keys + * @return static + */ + public function select($keys) + { + if ($keys instanceof Enumerable) { + $keys = $keys->all(); + } elseif (! is_null($keys)) { + $keys = is_array($keys) ? $keys : func_get_args(); + } + + return new static(function () use ($keys) { + if (is_null($keys)) { + yield from $this; + } else { + foreach ($this as $item) { + $itemKeys = array_flip($keys); + + $result = []; + + foreach ($item as $key => $value) { + if (array_key_exists($key, $itemKeys)) { + $result[$key] = $value; + + unset($itemKeys[$key]); + + if (empty($itemKeys)) { + continue; + } + } + } + + yield $result; + } + } + }); + } + /** * Push all of the given items onto the collection. * diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index dc82699b9329..e6b02c8dcfed 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -4228,6 +4228,37 @@ public function testOnly($collection) $this->assertEquals(['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], $data->only(collect(['first', 'email']))->all()); } + /** + * @dataProvider collectionClassProvider + */ + public function testSelect($collection) + { + $data = new $collection([ + ['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'last' => 'Archer', 'email' => 'jessarcher@gmail.com'], + ]); + + $this->assertEquals($data->all(), $data->select(null)->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select(['first', 'missing'])->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select('first', 'missing')->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select(collect(['first', 'missing']))->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select(['first', 'email'])->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select('first', 'email')->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select(collect(['first', 'email']))->all()); + } + /** * @dataProvider collectionClassProvider */ From 6ded1b813de45f5663f6f6526d622ebe947a70b5 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 1 Feb 2024 16:32:22 +0100 Subject: [PATCH 077/325] Fix releases workflow --- .github/workflows/releases.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index d5d873b08e85..43c7f2611a4a 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -51,15 +51,14 @@ jobs: - name: Cleanup release notes run: | - sed -i '/## What\'\''s Changed/q' ${{ steps.notes.outputs.release-notes }} - sed -i '/## Contributors/,$d' ${{ steps.notes.outputs.release-notes }} + sed -i '/## What'"'"'s Changed/d' ${{ steps.notes.outputs.release-notes }} + sed -i '/## New Contributors/,//d' ${{ steps.notes.outputs.release-notes }} - name: Create release - id: create_release - uses: actions/create-release@v1 + uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: v${{ steps.version.outputs.version }} - release_name: v${{ steps.version.outputs.version }} + name: v${{ steps.version.outputs.version }} body: ${{ steps.notes.outputs.release-notes }} From 3734ae98a6048c954eec960b46127fb8b9a32bad Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Thu, 1 Feb 2024 21:47:19 +0330 Subject: [PATCH 078/325] Update UrlGenerator.php (#49944) --- src/Illuminate/Routing/UrlGenerator.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Routing/UrlGenerator.php b/src/Illuminate/Routing/UrlGenerator.php index 0b9dcec33700..74c40d87cbbd 100755 --- a/src/Illuminate/Routing/UrlGenerator.php +++ b/src/Illuminate/Routing/UrlGenerator.php @@ -194,9 +194,7 @@ public function previousPath($fallback = false) */ protected function getPreviousUrlFromSession() { - $session = $this->getSession(); - - return $session ? $session->previousUrl() : null; + return $this->getSession()?->previousUrl(); } /** From c4f46abce0e69737de35b71439943ac2a24d6ec3 Mon Sep 17 00:00:00 2001 From: Adrien Foulon <6115458+Tofandel@users.noreply.github.com> Date: Fri, 2 Feb 2024 17:51:07 +0100 Subject: [PATCH 079/325] [10.x] Add POSIX compliant cleanup to artisan serve (#49943) * Add POSIX compliant cleanup to php artisan serve Fixes #49941 * Style CI * Move cleanup to a better location * Use built in server abstraction * Update ServeCommand.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Foundation/Console/ServeCommand.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Illuminate/Foundation/Console/ServeCommand.php b/src/Illuminate/Foundation/Console/ServeCommand.php index 194c80fa5127..416c8a2801ae 100644 --- a/src/Illuminate/Foundation/Console/ServeCommand.php +++ b/src/Illuminate/Foundation/Console/ServeCommand.php @@ -140,6 +140,14 @@ protected function startProcess($hasEnvironment) return in_array($key, static::$passthroughVariables) ? [$key => $value] : [$key => false]; })->all()); + $this->trap([SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, SIGQUIT], function ($signal) use ($process) { + if ($process->isRunning()) { + $process->stop(10, $signal); + } + + exit; + }); + $process->start($this->handleProcessOutput()); return $process; From e0dbf49e06530b8417d18fbeaf8f4f581a0fe724 Mon Sep 17 00:00:00 2001 From: Mateus Junges Date: Sun, 4 Feb 2024 22:46:54 -0300 Subject: [PATCH 080/325] Update call to get grammar (#49972) --- .../Database/Eloquent/Concerns/QueriesRelationships.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php index d9608f2f6c21..de43f9839824 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php +++ b/src/Illuminate/Database/Eloquent/Concerns/QueriesRelationships.php @@ -630,8 +630,8 @@ public function withAggregate($relations, $column, $function = null) $relation = $this->getRelationWithoutConstraints($name); if ($function) { - if ($this->getGrammar()->isExpression($column)) { - $aggregateColumn = $this->getGrammar()->getValue($column); + if ($this->getQuery()->getGrammar()->isExpression($column)) { + $aggregateColumn = $this->getQuery()->getGrammar()->getValue($column); } else { $hashedColumn = $this->getRelationHashedColumn($column, $relation); @@ -642,7 +642,7 @@ public function withAggregate($relations, $column, $function = null) $expression = $function === 'exists' ? $aggregateColumn : sprintf('%s(%s)', $function, $aggregateColumn); } else { - $expression = $this->getGrammar()->getValue($column); + $expression = $this->getQuery()->getGrammar()->getValue($column); } // Here, we will grab the relationship sub-query and prepare to add it to the main query @@ -671,7 +671,7 @@ public function withAggregate($relations, $column, $function = null) // the query builder. Then, we will return the builder instance back to the developer // for further constraint chaining that needs to take place on the query as needed. $alias ??= Str::snake( - preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function {$this->getGrammar()->getValue($column)}") + preg_replace('/[^[:alnum:][:space:]_]/u', '', "$name $function {$this->getQuery()->getGrammar()->getValue($column)}") ); if ($function === 'exists') { From 9448e01699d43761e8bcf3d2ff53d0e3efb37432 Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Mon, 5 Feb 2024 01:48:05 +0000 Subject: [PATCH 081/325] Adds PHPUnit 11 as conflict (#49957) --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 40255ad7b83f..c2559dd1af64 100644 --- a/composer.json +++ b/composer.json @@ -121,7 +121,8 @@ "conflict": { "carbonphp/carbon-doctrine-types": ">=3.0", "doctrine/dbal": ">=4.0", - "tightenco/collect": "<5.5.33" + "tightenco/collect": "<5.5.33", + "phpunit/phpunit": ">=11.0.0" }, "autoload": { "files": [ From a98b0219e125a0fe463104260f77141192ce6641 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 8 Feb 2024 15:42:42 +0100 Subject: [PATCH 082/325] Revert "[10.x] fix Before/After validation rules (#49871)" (#50013) This reverts commit cc653b080465072df501a313171acc8fc9615cad. --- .../Validation/Concerns/ValidatesAttributes.php | 8 +------- tests/Validation/ValidationValidatorTest.php | 6 ------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index c95a9f4f9999..b4335fe90059 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -253,13 +253,7 @@ protected function compareDates($attribute, $value, $parameters, $operator) } if (is_null($date = $this->getDateTimestamp($parameters[0]))) { - $comparedValue = $this->getValue($parameters[0]); - - if (! is_string($comparedValue) && ! is_numeric($comparedValue) && ! $comparedValue instanceof DateTimeInterface) { - return false; - } - - $date = $this->getDateTimestamp($comparedValue); + $date = $this->getDateTimestamp($this->getValue($parameters[0])); } return $this->compare($this->getDateTimestamp($value), $date, $operator); diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index e351c118eb30..7c9d7b31d3a2 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -5980,12 +5980,6 @@ public function testBeforeAndAfter() $v = new Validator($trans, ['x' => '0001-01-01T00:00'], ['x' => 'after:1970-01-01']); $this->assertTrue($v->fails()); - - $v = new Validator($trans, ['x' => ['a' => ['v' => 'c']], 'y' => 'invalid'], ['x' => 'date', 'y' => 'date|after:x']); - $this->assertTrue($v->fails()); - - $v = new Validator($trans, ['x' => ['a' => ['v' => 'c']], 'y' => 'invalid'], ['x' => 'date', 'y' => 'date|before:x']); - $this->assertTrue($v->fails()); } public function testBeforeAndAfterWithFormat() From f84d681a41bb6d01b403fc68a1b1f37da846b972 Mon Sep 17 00:00:00 2001 From: Daniele Faraglia Date: Thu, 8 Feb 2024 14:47:17 +0000 Subject: [PATCH 083/325] fix the phpdoc for replaceMatches in Str and Stringable helpers (#49990) --- src/Illuminate/Support/Str.php | 2 +- src/Illuminate/Support/Stringable.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index fc444ba699c7..56ed9a22c98c 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1188,7 +1188,7 @@ public static function replaceEnd($search, $replace, $subject) /** * Replace the patterns matching the given regular expression. * - * @param string $pattern + * @param array|string $pattern * @param \Closure|string $replace * @param array|string $subject * @param int $limit diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index cc1157b5d496..eb7e3638d1bd 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -720,7 +720,7 @@ public function replaceEnd($search, $replace) /** * Replace the patterns matching the given regular expression. * - * @param string $pattern + * @param array|string $pattern * @param \Closure|string $replace * @param int $limit * @return static From b474a36bd1b72e33894c6abfb47cc4a2c941bb96 Mon Sep 17 00:00:00 2001 From: Rijoanul Hasan Shanto Date: Thu, 8 Feb 2024 20:52:52 +0600 Subject: [PATCH 084/325] Added setAbly() method for AblyBroadcaster (#49981) --- .../Broadcasting/Broadcasters/AblyBroadcaster.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php b/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php index a1e283a32c96..01c673c22f32 100644 --- a/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php +++ b/src/Illuminate/Broadcasting/Broadcasters/AblyBroadcaster.php @@ -232,4 +232,15 @@ public function getAbly() { return $this->ably; } + + /** + * Set the underlying Ably SDK instance. + * + * @param \Ably\AblyRest $ably + * @return void + */ + public function setAbly($ably) + { + $this->ably = $ably; + } } From 0c830e7a70b01ba6149c1795b77f1d32eef8d624 Mon Sep 17 00:00:00 2001 From: Kyryll Kovalenko Date: Thu, 8 Feb 2024 16:10:07 +0100 Subject: [PATCH 085/325] [10.x] Fix in appendExceptionToException method exception type check (#49958) * Fix in appendExceptionToException method exception type check * Update TestResponse.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Testing/TestResponse.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index ea19db479a03..ebf06a7dc1fd 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -1651,7 +1651,7 @@ public function transformNotSuccessfulException($exception) */ protected function appendExceptionToException($exceptionToAppend, $exception) { - $exceptionMessage = $exceptionToAppend->getMessage(); + $exceptionMessage = is_string($exceptionToAppend) ? $exceptionToAppend : $exceptionToAppend->getMessage(); $exceptionToAppend = (string) $exceptionToAppend; From d4390fe2a937f0094bbcecbc0730477f10de1811 Mon Sep 17 00:00:00 2001 From: Jaanus Vapper Date: Thu, 8 Feb 2024 17:28:20 +0200 Subject: [PATCH 086/325] DB command: add sqlcmd -C flag when 'trust_server_certificate' is set (#49952) --- src/Illuminate/Database/Console/DbCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Database/Console/DbCommand.php b/src/Illuminate/Database/Console/DbCommand.php index caecafe3a644..c9d3b5290907 100644 --- a/src/Illuminate/Database/Console/DbCommand.php +++ b/src/Illuminate/Database/Console/DbCommand.php @@ -192,6 +192,7 @@ protected function getSqlsrvArguments(array $connection) 'password' => ['-P', $connection['password']], 'host' => ['-S', 'tcp:'.$connection['host'] .($connection['port'] ? ','.$connection['port'] : ''), ], + 'trust_server_certificate' => ['-C'], ], $connection)); } From 7fac93b005053fb497df2d11f9d3c8ada4f1a221 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 8 Feb 2024 23:42:39 +0800 Subject: [PATCH 087/325] Allows Setup and Teardown actions to be reused in alternative TestCase for Laravel (#49973) * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * formatting --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot Co-authored-by: Taylor Otwell --- .../InteractsWithTestCaseLifecycle.php | 282 ++++++++++++++++++ .../Foundation/Testing/TestCase.php | 239 +-------------- 2 files changed, 286 insertions(+), 235 deletions(-) create mode 100644 src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php new file mode 100644 index 000000000000..42a929a51388 --- /dev/null +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php @@ -0,0 +1,282 @@ +app) { + $this->refreshApplication(); + + ParallelTesting::callSetUpTestCaseCallbacks($this); + } + + $this->setUpTraits(); + + foreach ($this->afterApplicationCreatedCallbacks as $callback) { + $callback(); + } + + Model::setEventDispatcher($this->app['events']); + + $this->setUpHasRun = true; + } + + /** + * Clean up the testing environment before the next test. + * + * @internal + * + * @return void + */ + protected function tearDownTheTestEnvironment(): void + { + if ($this->app) { + $this->callBeforeApplicationDestroyedCallbacks(); + + ParallelTesting::callTearDownTestCaseCallbacks($this); + + $this->app->flush(); + + $this->app = null; + } + + $this->setUpHasRun = false; + + if (property_exists($this, 'serverVariables')) { + $this->serverVariables = []; + } + + if (property_exists($this, 'defaultHeaders')) { + $this->defaultHeaders = []; + } + + if (class_exists('Mockery')) { + if ($container = Mockery::getContainer()) { + $this->addToAssertionCount($container->mockery_getExpectationCount()); + } + + try { + Mockery::close(); + } catch (InvalidCountException $e) { + if (! Str::contains($e->getMethodName(), ['doWrite', 'askQuestion'])) { + throw $e; + } + } + } + + if (class_exists(Carbon::class)) { + Carbon::setTestNow(); + } + + if (class_exists(CarbonImmutable::class)) { + CarbonImmutable::setTestNow(); + } + + $this->afterApplicationCreatedCallbacks = []; + $this->beforeApplicationDestroyedCallbacks = []; + + $this->originalExceptionHandler = null; + $this->originalDeprecationHandler = null; + + AboutCommand::flushState(); + Artisan::forgetBootstrappers(); + Component::flushCache(); + Component::forgetComponentsResolver(); + Component::forgetFactory(); + ConvertEmptyStringsToNull::flushState(); + HandleExceptions::forgetApp(); + Queue::createPayloadUsing(null); + Sleep::fake(false); + TrimStrings::flushState(); + + if ($this->callbackException) { + throw $this->callbackException; + } + } + + /** + * Boot the testing helper traits. + * + * @return array + */ + protected function setUpTraits() + { + $uses = array_flip(class_uses_recursive(static::class)); + + if (isset($uses[RefreshDatabase::class])) { + $this->refreshDatabase(); + } + + if (isset($uses[DatabaseMigrations::class])) { + $this->runDatabaseMigrations(); + } + + if (isset($uses[DatabaseTruncation::class])) { + $this->truncateDatabaseTables(); + } + + if (isset($uses[DatabaseTransactions::class])) { + $this->beginDatabaseTransaction(); + } + + if (isset($uses[WithoutMiddleware::class])) { + $this->disableMiddlewareForAllTests(); + } + + if (isset($uses[WithoutEvents::class])) { + $this->disableEventsForAllTests(); + } + + if (isset($uses[WithFaker::class])) { + $this->setUpFaker(); + } + + foreach ($uses as $trait) { + if (method_exists($this, $method = 'setUp'.class_basename($trait))) { + $this->{$method}(); + } + + if (method_exists($this, $method = 'tearDown'.class_basename($trait))) { + $this->beforeApplicationDestroyed(fn () => $this->{$method}()); + } + } + + return $uses; + } + + /** + * Clean up the testing environment before the next test case. + * + * @internal + * + * @return void + */ + public static function tearDownAfterClassUsingTestCase() + { + foreach ([ + \PHPUnit\Util\Annotation\Registry::class, + \PHPUnit\Metadata\Annotation\Parser\Registry::class, + ] as $class) { + if (class_exists($class)) { + (function () { + $this->classDocBlocks = []; + $this->methodDocBlocks = []; + })->call($class::getInstance()); + } + } + } + + /** + * Register a callback to be run after the application is created. + * + * @param callable $callback + * @return void + */ + public function afterApplicationCreated(callable $callback) + { + $this->afterApplicationCreatedCallbacks[] = $callback; + + if ($this->setUpHasRun) { + $callback(); + } + } + + /** + * Register a callback to be run before the application is destroyed. + * + * @param callable $callback + * @return void + */ + protected function beforeApplicationDestroyed(callable $callback) + { + $this->beforeApplicationDestroyedCallbacks[] = $callback; + } + + /** + * Execute the application's pre-destruction callbacks. + * + * @return void + */ + protected function callBeforeApplicationDestroyedCallbacks() + { + foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { + try { + $callback(); + } catch (Throwable $e) { + if (! $this->callbackException) { + $this->callbackException = $e; + } + } + } + } +} diff --git a/src/Illuminate/Foundation/Testing/TestCase.php b/src/Illuminate/Foundation/Testing/TestCase.php index 152b5c4131ae..0b4e9367bc25 100644 --- a/src/Illuminate/Foundation/Testing/TestCase.php +++ b/src/Illuminate/Foundation/Testing/TestCase.php @@ -2,22 +2,6 @@ namespace Illuminate\Foundation\Testing; -use Carbon\CarbonImmutable; -use Illuminate\Console\Application as Artisan; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Foundation\Bootstrap\HandleExceptions; -use Illuminate\Foundation\Console\AboutCommand; -use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull; -use Illuminate\Foundation\Http\Middleware\TrimStrings; -use Illuminate\Queue\Queue; -use Illuminate\Support\Carbon; -use Illuminate\Support\Facades\Facade; -use Illuminate\Support\Facades\ParallelTesting; -use Illuminate\Support\Sleep; -use Illuminate\Support\Str; -use Illuminate\View\Component; -use Mockery; -use Mockery\Exception\InvalidCountException; use PHPUnit\Framework\TestCase as BaseTestCase; use Throwable; @@ -32,43 +16,9 @@ abstract class TestCase extends BaseTestCase Concerns\InteractsWithExceptionHandling, Concerns\InteractsWithSession, Concerns\InteractsWithTime, + Concerns\InteractsWithTestCaseLifecycle, Concerns\InteractsWithViews; - /** - * The Illuminate application instance. - * - * @var \Illuminate\Foundation\Application - */ - protected $app; - - /** - * The callbacks that should be run after the application is created. - * - * @var array - */ - protected $afterApplicationCreatedCallbacks = []; - - /** - * The callbacks that should be run before the application is destroyed. - * - * @var array - */ - protected $beforeApplicationDestroyedCallbacks = []; - - /** - * The exception thrown while running an application destruction callback. - * - * @var \Throwable - */ - protected $callbackException; - - /** - * Indicates if we have made it through the base setUp function. - * - * @var bool - */ - protected $setUpHasRun = false; - /** * Creates the application. * @@ -87,23 +37,7 @@ protected function setUp(): void { static::$latestResponse = null; - Facade::clearResolvedInstances(); - - if (! $this->app) { - $this->refreshApplication(); - - ParallelTesting::callSetUpTestCaseCallbacks($this); - } - - $this->setUpTraits(); - - foreach ($this->afterApplicationCreatedCallbacks as $callback) { - $callback(); - } - - Model::setEventDispatcher($this->app['events']); - - $this->setUpHasRun = true; + $this->setUpTheTestEnvironment(); } /** @@ -116,56 +50,6 @@ protected function refreshApplication() $this->app = $this->createApplication(); } - /** - * Boot the testing helper traits. - * - * @return array - */ - protected function setUpTraits() - { - $uses = array_flip(class_uses_recursive(static::class)); - - if (isset($uses[RefreshDatabase::class])) { - $this->refreshDatabase(); - } - - if (isset($uses[DatabaseMigrations::class])) { - $this->runDatabaseMigrations(); - } - - if (isset($uses[DatabaseTruncation::class])) { - $this->truncateDatabaseTables(); - } - - if (isset($uses[DatabaseTransactions::class])) { - $this->beginDatabaseTransaction(); - } - - if (isset($uses[WithoutMiddleware::class])) { - $this->disableMiddlewareForAllTests(); - } - - if (isset($uses[WithoutEvents::class])) { - $this->disableEventsForAllTests(); - } - - if (isset($uses[WithFaker::class])) { - $this->setUpFaker(); - } - - foreach ($uses as $trait) { - if (method_exists($this, $method = 'setUp'.class_basename($trait))) { - $this->{$method}(); - } - - if (method_exists($this, $method = 'tearDown'.class_basename($trait))) { - $this->beforeApplicationDestroyed(fn () => $this->{$method}()); - } - } - - return $uses; - } - /** * {@inheritdoc} */ @@ -195,68 +79,7 @@ protected function runTest(): mixed */ protected function tearDown(): void { - if ($this->app) { - $this->callBeforeApplicationDestroyedCallbacks(); - - ParallelTesting::callTearDownTestCaseCallbacks($this); - - $this->app->flush(); - - $this->app = null; - } - - $this->setUpHasRun = false; - - if (property_exists($this, 'serverVariables')) { - $this->serverVariables = []; - } - - if (property_exists($this, 'defaultHeaders')) { - $this->defaultHeaders = []; - } - - if (class_exists('Mockery')) { - if ($container = Mockery::getContainer()) { - $this->addToAssertionCount($container->mockery_getExpectationCount()); - } - - try { - Mockery::close(); - } catch (InvalidCountException $e) { - if (! Str::contains($e->getMethodName(), ['doWrite', 'askQuestion'])) { - throw $e; - } - } - } - - if (class_exists(Carbon::class)) { - Carbon::setTestNow(); - } - - if (class_exists(CarbonImmutable::class)) { - CarbonImmutable::setTestNow(); - } - - $this->afterApplicationCreatedCallbacks = []; - $this->beforeApplicationDestroyedCallbacks = []; - - $this->originalExceptionHandler = null; - $this->originalDeprecationHandler = null; - - AboutCommand::flushState(); - Artisan::forgetBootstrappers(); - Component::flushCache(); - Component::forgetComponentsResolver(); - Component::forgetFactory(); - ConvertEmptyStringsToNull::flushState(); - HandleExceptions::forgetApp(); - Queue::createPayloadUsing(null); - Sleep::fake(false); - TrimStrings::flushState(); - - if ($this->callbackException) { - throw $this->callbackException; - } + $this->tearDownTheTestEnvironment(); } /** @@ -268,60 +91,6 @@ public static function tearDownAfterClass(): void { static::$latestResponse = null; - foreach ([ - \PHPUnit\Util\Annotation\Registry::class, - \PHPUnit\Metadata\Annotation\Parser\Registry::class, - ] as $class) { - if (class_exists($class)) { - (function () { - $this->classDocBlocks = []; - $this->methodDocBlocks = []; - })->call($class::getInstance()); - } - } - } - - /** - * Register a callback to be run after the application is created. - * - * @param callable $callback - * @return void - */ - public function afterApplicationCreated(callable $callback) - { - $this->afterApplicationCreatedCallbacks[] = $callback; - - if ($this->setUpHasRun) { - $callback(); - } - } - - /** - * Register a callback to be run before the application is destroyed. - * - * @param callable $callback - * @return void - */ - protected function beforeApplicationDestroyed(callable $callback) - { - $this->beforeApplicationDestroyedCallbacks[] = $callback; - } - - /** - * Execute the application's pre-destruction callbacks. - * - * @return void - */ - protected function callBeforeApplicationDestroyedCallbacks() - { - foreach ($this->beforeApplicationDestroyedCallbacks as $callback) { - try { - $callback(); - } catch (Throwable $e) { - if (! $this->callbackException) { - $this->callbackException = $e; - } - } - } + static::tearDownAfterClassUsingTestCase(); } } From 1d0f002172d1570389e72fb474d16a6996fc943d Mon Sep 17 00:00:00 2001 From: Mark Townsend Date: Thu, 8 Feb 2024 09:54:49 -0600 Subject: [PATCH 088/325] [10.x] Add `toBase64()` and `fromBase64()` methods to Stringable and Str classes (#49984) * Add toBase64 and fromBase64 methods to Stringable * Update SupportStringableTest.php * Call __toString * Styleci * More styleci shenannys * Adjust test * Add toBase64 & fromBase64 to Str class + tests * Styleci fix * Update Stringable.php * Update Str.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Support/Str.php | 23 +++++++++++++++++++++++ src/Illuminate/Support/Stringable.php | 21 +++++++++++++++++++++ tests/Support/SupportStrTest.php | 12 ++++++++++++ tests/Support/SupportStringableTest.php | 14 ++++++++++++++ 4 files changed, 70 insertions(+) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 56ed9a22c98c..b970d421d0f2 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1535,6 +1535,29 @@ public static function take($string, int $limit): string return static::substr($string, 0, $limit); } + /** + * Convert the given string to Base64 encoding. + * + * @param string $string + * @return string + */ + public static function toBase64($string): string + { + return base64_encode($string); + } + + /** + * Decode the given Base64 encoded string. + * + * @param string $string + * @param bool $strict + * @return void + */ + public static function fromBase64($string, $strict = false) + { + return base64_decode($string, $strict); + } + /** * Make a string's first character lowercase. * diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index eb7e3638d1bd..6352bb62b200 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -1246,6 +1246,27 @@ public function toHtmlString() return new HtmlString($this->value); } + /** + * Convert the string to Base64 encoding. + * + * @return void + */ + public function toBase64() + { + return new static(base64_encode($this->value)); + } + + /** + * Decode the Base64 encoded string. + * + * @param bool $strict + * @return void + */ + public function fromBase64($strict = false) + { + return new static(base64_decode($this->value, $strict)); + } + /** * Dump the string. * diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index af0523b935fd..c9f6c0b0c63a 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -1370,6 +1370,18 @@ public function testPasswordCreation() Str::of(Str::password())->contains(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']) ); } + + public function testToBase64() + { + $this->assertSame(base64_encode('foo'), Str::toBase64('foo')); + $this->assertSame(base64_encode('foobar'), Str::toBase64('foobar')); + } + + public function testFromBase64() + { + $this->assertSame('foo', Str::fromBase64(base64_encode('foo'))); + $this->assertSame('foobar', Str::fromBase64(base64_encode('foobar'), true)); + } } class StringableObjectStub diff --git a/tests/Support/SupportStringableTest.php b/tests/Support/SupportStringableTest.php index 5192a62cc002..6165bee79944 100644 --- a/tests/Support/SupportStringableTest.php +++ b/tests/Support/SupportStringableTest.php @@ -1302,4 +1302,18 @@ public function testArrayAccess() $this->assertTrue(isset($str[2])); $this->assertFalse(isset($str[10])); } + + public function testToBase64() + { + $this->assertSame(base64_encode('foo'), (string) $this->stringable('foo')->toBase64()); + $this->assertSame(base64_encode('foobar'), (string) $this->stringable('foobar')->toBase64()); + $this->assertSame(base64_encode('foobarbaz'), (string) $this->stringable('foobarbaz')->toBase64()); + } + + public function testFromBase64() + { + $this->assertSame('foo', (string) $this->stringable(base64_encode('foo'))->fromBase64()); + $this->assertSame('foobar', (string) $this->stringable(base64_encode('foobar'))->fromBase64(true)); + $this->assertSame('foobarbaz', (string) $this->stringable(base64_encode('foobarbaz'))->fromBase64()); + } } From e18ca8ffe4f3956c51487d53216c2865ab47f0b7 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 9 Feb 2024 23:09:16 +0800 Subject: [PATCH 089/325] [10.x] Allows to defer resolving pcntl only if it's available (#50024) * [10.x] Allows to defer resolving pcntl only if it's available fixes #50022 Signed-off-by: Mior Muhammad Zaki * wip --------- Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Console/Concerns/InteractsWithSignals.php | 6 ++++-- src/Illuminate/Foundation/Console/ServeCommand.php | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Console/Concerns/InteractsWithSignals.php b/src/Illuminate/Console/Concerns/InteractsWithSignals.php index 895072c15c72..c93b98dc4e6d 100644 --- a/src/Illuminate/Console/Concerns/InteractsWithSignals.php +++ b/src/Illuminate/Console/Concerns/InteractsWithSignals.php @@ -17,7 +17,9 @@ trait InteractsWithSignals /** * Define a callback to be run when the given signal(s) occurs. * - * @param iterable|int $signals + * @template TSignals of iterable|int + * + * @param (\Closure():(TSignals))|TSignals $signals * @param callable(int $signal): void $callback * @return void */ @@ -28,7 +30,7 @@ public function trap($signals, $callback) $this->getApplication()->getSignalRegistry(), ); - collect(Arr::wrap($signals)) + collect(Arr::wrap(value($signals))) ->each(fn ($signal) => $this->signals->register($signal, $callback)); }); } diff --git a/src/Illuminate/Foundation/Console/ServeCommand.php b/src/Illuminate/Foundation/Console/ServeCommand.php index 416c8a2801ae..45ba5f9f3629 100644 --- a/src/Illuminate/Foundation/Console/ServeCommand.php +++ b/src/Illuminate/Foundation/Console/ServeCommand.php @@ -140,7 +140,7 @@ protected function startProcess($hasEnvironment) return in_array($key, static::$passthroughVariables) ? [$key => $value] : [$key => false]; })->all()); - $this->trap([SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, SIGQUIT], function ($signal) use ($process) { + $this->trap(fn () => [SIGTERM, SIGINT, SIGHUP, SIGUSR1, SIGUSR2, SIGQUIT], function ($signal) use ($process) { if ($process->isRunning()) { $process->stop(10, $signal); } From 6821aa10ba0ee4697a336452a417a8f64205dd74 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 9 Feb 2024 23:09:37 +0800 Subject: [PATCH 090/325] [10.x] Fixes missing `Throwable` import and handle if `originalExceptionHandler` or `originalDeprecationHandler` property isn't used by alternative TestCase (#50021) * [10.x] Fixes missing `Throwable` import. Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot --- .../Concerns/InteractsWithTestCaseLifecycle.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php index 42a929a51388..eb699b3e967e 100644 --- a/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php +++ b/src/Illuminate/Foundation/Testing/Concerns/InteractsWithTestCaseLifecycle.php @@ -25,6 +25,7 @@ use Illuminate\View\Component; use Mockery; use Mockery\Exception\InvalidCountException; +use Throwable; trait InteractsWithTestCaseLifecycle { @@ -145,8 +146,13 @@ protected function tearDownTheTestEnvironment(): void $this->afterApplicationCreatedCallbacks = []; $this->beforeApplicationDestroyedCallbacks = []; - $this->originalExceptionHandler = null; - $this->originalDeprecationHandler = null; + if (property_exists($this, 'originalExceptionHandler')) { + $this->originalExceptionHandler = null; + } + + if (property_exists($this, 'originalDeprecationHandler')) { + $this->originalDeprecationHandler = null; + } AboutCommand::flushState(); Artisan::forgetBootstrappers(); From 24d0b5bd4efc52c1b093598a50d25c844913d81e Mon Sep 17 00:00:00 2001 From: lorenzolosa <11164571+lorenzolosa@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:11:01 -0500 Subject: [PATCH 091/325] [10.x] Type hinting for conditional validation rules (#50017) * Type hinting for conditional validation rules * change types order --- src/Illuminate/Validation/ConditionalRules.php | 8 ++++---- src/Illuminate/Validation/Rule.php | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Validation/ConditionalRules.php b/src/Illuminate/Validation/ConditionalRules.php index 02f8e21fb7fa..fa6022209672 100644 --- a/src/Illuminate/Validation/ConditionalRules.php +++ b/src/Illuminate/Validation/ConditionalRules.php @@ -16,14 +16,14 @@ class ConditionalRules /** * The rules to be added to the attribute. * - * @var array|string|\Closure + * @var \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string */ protected $rules; /** * The rules to be added to the attribute if the condition fails. * - * @var array|string|\Closure + * @var \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string */ protected $defaultRules; @@ -31,8 +31,8 @@ class ConditionalRules * Create a new conditional rules instance. * * @param callable|bool $condition - * @param array|string|\Closure $rules - * @param array|string|\Closure $defaultRules + * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string $rules + * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string $defaultRules * @return void */ public function __construct($condition, $rules, $defaultRules = []) diff --git a/src/Illuminate/Validation/Rule.php b/src/Illuminate/Validation/Rule.php index df28c8938c60..349e1453c8f6 100644 --- a/src/Illuminate/Validation/Rule.php +++ b/src/Illuminate/Validation/Rule.php @@ -37,8 +37,8 @@ public static function can($ability, ...$arguments) * Apply the given rules if the given condition is truthy. * * @param callable|bool $condition - * @param array|string|\Closure $rules - * @param array|string|\Closure $defaultRules + * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string $rules + * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string $defaultRules * @return \Illuminate\Validation\ConditionalRules */ public static function when($condition, $rules, $defaultRules = []) @@ -50,8 +50,8 @@ public static function when($condition, $rules, $defaultRules = []) * Apply the given rules if the given condition is falsy. * * @param callable|bool $condition - * @param array|string|\Closure $rules - * @param array|string|\Closure $defaultRules + * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string $rules + * @param \Illuminate\Contracts\Validation\ValidationRule|\Illuminate\Contracts\Validation\InvokableRule|\Illuminate\Contracts\Validation\Rule|\Closure|array|string $defaultRules * @return \Illuminate\Validation\ConditionalRules */ public static function unless($condition, $rules, $defaultRules = []) From 0a8c91bdc9efeb22b1e2959f19cfaec6b78556dc Mon Sep 17 00:00:00 2001 From: Ryan Chandler Date: Fri, 9 Feb 2024 15:56:19 +0000 Subject: [PATCH 092/325] [10.x] Introduce new `Arr::take()` helper (#50015) * Support/Arr: Introduce new Arr::limit() helper * Arr: Rename limit() to take() for consistency --- src/Illuminate/Collections/Arr.php | 16 ++++++++++++++++ tests/Support/SupportArrTest.php | 13 +++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 482bbadf0fb0..f51accf1e408 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -225,6 +225,22 @@ public static function last($array, callable $callback = null, $default = null) return static::first(array_reverse($array, true), $callback, $default); } + /** + * Take the first or last {$limit} items from an array. + * + * @param array $array + * @param int $limit + * @return array + */ + public static function take($array, $limit) + { + if ($limit < 0) { + return array_slice($array, $limit, abs($limit)); + } + + return array_slice($array, 0, $limit); + } + /** * Flatten a multi-dimensional array into a single level. * diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 083ca5bbb306..05f6b743f3e1 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -1260,4 +1260,17 @@ public function testPrependKeysWith() ], ], Arr::prependKeysWith($array, 'test.')); } + + public function testTake() + { + $array = [1, 2, 3, 4, 5, 6]; + + $this->assertEquals([ + 1, 2, 3, + ], Arr::take($array, 3)); + + $this->assertEquals([ + 4, 5, 6, + ], Arr::take($array, -3)); + } } From 856d1bf7ad4381afff23f84d36177f9c3b035998 Mon Sep 17 00:00:00 2001 From: Jeremias Wolff Date: Fri, 9 Feb 2024 17:25:46 +0100 Subject: [PATCH 093/325] [10.x] Improved Handling of Empty Component Slots with HTML Comments or Line Breaks (#49966) * implement alternative solution for PR#49935 * style ci * style ci * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/View/ComponentSlot.php | 20 +++++++++ tests/View/ComponentTest.php | 58 +++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/Illuminate/View/ComponentSlot.php b/src/Illuminate/View/ComponentSlot.php index 85665ad64575..dc48dbc88798 100644 --- a/src/Illuminate/View/ComponentSlot.php +++ b/src/Illuminate/View/ComponentSlot.php @@ -3,6 +3,7 @@ namespace Illuminate\View; use Illuminate\Contracts\Support\Htmlable; +use InvalidArgumentException; class ComponentSlot implements Htmlable { @@ -77,6 +78,25 @@ public function isNotEmpty() return ! $this->isEmpty(); } + /** + * Determine if the slot has non-comment content. + * + * @param callable|string|null $callable + * @return bool + */ + public function hasActualContent(callable|string|null $callable = null) + { + if (is_string($callable) && ! function_exists($callable)) { + throw new InvalidArgumentException('Callable does not exist.'); + } + + return filter_var( + $this->contents, + FILTER_CALLBACK, + ['options' => $callable ?? fn ($input) => trim(preg_replace("//", '', $input))] + ) !== ''; + } + /** * Get the slot's HTML string. * diff --git a/tests/View/ComponentTest.php b/tests/View/ComponentTest.php index a5ff36d82531..87e716edcee7 100644 --- a/tests/View/ComponentTest.php +++ b/tests/View/ComponentTest.php @@ -11,6 +11,7 @@ use Illuminate\Support\Facades\Facade; use Illuminate\Support\HtmlString; use Illuminate\View\Component; +use Illuminate\View\ComponentSlot; use Illuminate\View\Factory; use Illuminate\View\View; use Mockery as m; @@ -305,6 +306,63 @@ public function testFactoryGetsSharedBetweenComponents() Component::forgetFactory(); $this->assertNotSame($this->viewFactory, $getFactory($inline)); } + + public function testComponentSlotIsEmpty() + { + $slot = new ComponentSlot(); + + $this->assertTrue((bool) $slot->isEmpty()); + } + + public function testComponentSlotSanitizedEmpty() + { + // default sanitizer should remove all html tags + $slot = new ComponentSlot(''); + + $linebreakingSlot = new ComponentSlot("\n \t"); + + $moreComplexSlot = new ComponentSlot(''); + + $this->assertFalse((bool) $slot->hasActualContent()); + $this->assertFalse((bool) $linebreakingSlot->hasActualContent('trim')); + $this->assertFalse((bool) $moreComplexSlot->hasActualContent()); + } + + public function testComponentSlotSanitizedNotEmpty() + { + // default sanitizer should remove all html tags + $slot = new ComponentSlot('not empty'); + + $linebreakingSlot = new ComponentSlot("\ntest \t"); + + $moreComplexSlot = new ComponentSlot('beforeafter'); + + $this->assertTrue((bool) $slot->hasActualContent()); + $this->assertTrue((bool) $linebreakingSlot->hasActualContent('trim')); + $this->assertTrue((bool) $moreComplexSlot->hasActualContent()); + } + + public function testComponentSlotIsNotEmpty() + { + $slot = new ComponentSlot('test'); + + $anotherSlot = new ComponentSlot('test'); + + $moreComplexSlot = new ComponentSlot('test'); + + $this->assertTrue((bool) $slot->hasActualContent()); + $this->assertTrue((bool) $anotherSlot->hasActualContent()); + $this->assertTrue((bool) $moreComplexSlot->hasActualContent()); + } } class TestInlineViewComponent extends Component From 6c9cbc9ba0437bcfe54609ae0af312feefc7e309 Mon Sep 17 00:00:00 2001 From: Eliezer Margareten <46111162+emargareten@users.noreply.github.com> Date: Fri, 9 Feb 2024 19:16:23 +0200 Subject: [PATCH 094/325] [10.x] Introduce Observe attribute for models (#49843) * Introduce Observe attribute for models * fix tests * fix styling * rename file --------- Co-authored-by: Taylor Otwell --- .../Eloquent/Attributes/ObservedBy.php | 19 +++++++++++ .../Database/Eloquent/Concerns/HasEvents.php | 27 +++++++++++++++ ...ficationNotificationHandleFunctionTest.php | 5 +-- tests/Database/DatabaseEloquentModelTest.php | 33 +++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php diff --git a/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php b/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php new file mode 100644 index 000000000000..600174146f94 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Attributes/ObservedBy.php @@ -0,0 +1,19 @@ +getAttributes(ObservedBy::class)) + ->map(fn ($attribute) => $attribute->getArguments()) + ->flatten() + ->all(); + } + /** * Register observers with the model. * diff --git a/tests/Auth/AuthListenersSendEmailVerificationNotificationHandleFunctionTest.php b/tests/Auth/AuthListenersSendEmailVerificationNotificationHandleFunctionTest.php index 2b888f59f07e..9ef72a9fd726 100644 --- a/tests/Auth/AuthListenersSendEmailVerificationNotificationHandleFunctionTest.php +++ b/tests/Auth/AuthListenersSendEmailVerificationNotificationHandleFunctionTest.php @@ -6,6 +6,7 @@ use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Foundation\Auth\User; +use Mockery as m; use PHPUnit\Framework\TestCase; class AuthListenersSendEmailVerificationNotificationHandleFunctionTest extends TestCase @@ -29,8 +30,8 @@ public function testWillExecuted() */ public function testUserIsNotInstanceOfMustVerifyEmail() { - $user = $this->getMockBuilder(User::class)->getMock(); - $user->expects($this->never())->method('sendEmailVerificationNotification'); + $user = m::mock(User::class); + $user->shouldNotReceive('sendEmailVerificationNotification'); $listener = new SendEmailVerificationNotification; diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 395629b4f9b1..1966e5550bee 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -15,6 +15,7 @@ use Illuminate\Database\Connection; use Illuminate\Database\ConnectionResolverInterface; use Illuminate\Database\ConnectionResolverInterface as Resolver; +use Illuminate\Database\Eloquent\Attributes\ObservedBy; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\ArrayObject; use Illuminate\Database\Eloquent\Casts\AsArrayObject; @@ -1893,6 +1894,26 @@ public function testModelObserversCanBeAttachedToModelsThroughAnArray() EloquentModelStub::flushEventListeners(); } + public function testModelObserversCanBeAttachedToModelsWithStringUsingAttribute() + { + EloquentModelWithObserveAttributeStub::setEventDispatcher($events = m::mock(Dispatcher::class)); + $events->shouldReceive('dispatch'); + $events->shouldReceive('listen')->once()->with('eloquent.creating: Illuminate\Tests\Database\EloquentModelWithObserveAttributeStub', EloquentTestObserverStub::class.'@creating'); + $events->shouldReceive('listen')->once()->with('eloquent.saved: Illuminate\Tests\Database\EloquentModelWithObserveAttributeStub', EloquentTestObserverStub::class.'@saved'); + $events->shouldReceive('forget'); + EloquentModelWithObserveAttributeStub::flushEventListeners(); + } + + public function testModelObserversCanBeAttachedToModelsThroughAnArrayUsingAttribute() + { + EloquentModelWithObserveAttributeUsingArrayStub::setEventDispatcher($events = m::mock(Dispatcher::class)); + $events->shouldReceive('dispatch'); + $events->shouldReceive('listen')->once()->with('eloquent.creating: Illuminate\Tests\Database\EloquentModelWithObserveAttributeUsingArrayStub', EloquentTestObserverStub::class.'@creating'); + $events->shouldReceive('listen')->once()->with('eloquent.saved: Illuminate\Tests\Database\EloquentModelWithObserveAttributeUsingArrayStub', EloquentTestObserverStub::class.'@saved'); + $events->shouldReceive('forget'); + EloquentModelWithObserveAttributeUsingArrayStub::flushEventListeners(); + } + public function testThrowExceptionOnAttachingNotExistsModelObserverWithString() { $this->expectException(InvalidArgumentException::class); @@ -3334,6 +3355,18 @@ public function uniqueIds() } } +#[ObservedBy(EloquentTestObserverStub::class)] +class EloquentModelWithObserveAttributeStub extends EloquentModelStub +{ + // +} + +#[ObservedBy([EloquentTestObserverStub::class])] +class EloquentModelWithObserveAttributeUsingArrayStub extends EloquentModelStub +{ + // +} + class EloquentModelSavingEventStub { // From 67a0e4716ab3cbf92528a41a8515dfb8d8bbaa37 Mon Sep 17 00:00:00 2001 From: Eliezer Margareten <46111162+emargareten@users.noreply.github.com> Date: Fri, 9 Feb 2024 17:58:04 -0500 Subject: [PATCH 095/325] [10.x] Add ScopedBy attribute for models (#50034) * [10.x] Introduce ScopedBy attribute for models * Update HasGlobalScopes.php --- .../Database/Eloquent/Attributes/ScopedBy.php | 19 +++++++++++++ .../Eloquent/Concerns/HasGlobalScopes.php | 27 +++++++++++++++++++ .../DatabaseEloquentGlobalScopesTest.php | 15 +++++++++++ 3 files changed, 61 insertions(+) create mode 100644 src/Illuminate/Database/Eloquent/Attributes/ScopedBy.php diff --git a/src/Illuminate/Database/Eloquent/Attributes/ScopedBy.php b/src/Illuminate/Database/Eloquent/Attributes/ScopedBy.php new file mode 100644 index 000000000000..747ed9e612a3 --- /dev/null +++ b/src/Illuminate/Database/Eloquent/Attributes/ScopedBy.php @@ -0,0 +1,19 @@ +getAttributes(ScopedBy::class)) + ->map(fn ($attribute) => $attribute->getArguments()) + ->flatten() + ->all(); + } + /** * Register a new global scope on the model. * diff --git a/tests/Database/DatabaseEloquentGlobalScopesTest.php b/tests/Database/DatabaseEloquentGlobalScopesTest.php index bef11fbcc854..3b5a379d4fdb 100644 --- a/tests/Database/DatabaseEloquentGlobalScopesTest.php +++ b/tests/Database/DatabaseEloquentGlobalScopesTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\Database; use Illuminate\Database\Capsule\Manager as DB; +use Illuminate\Database\Eloquent\Attributes\ScopedBy; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; @@ -51,6 +52,14 @@ public function testClassNameGlobalScopeIsApplied() $this->assertEquals([1], $query->getBindings()); } + public function testGlobalScopeInAttributeIsApplied() + { + $model = new EloquentGlobalScopeInAttributeTestModel; + $query = $model->newQuery(); + $this->assertSame('select * from "table" where "active" = ?', $query->toSql()); + $this->assertEquals([1], $query->getBindings()); + } + public function testClosureGlobalScopeIsApplied() { $model = new EloquentClosureGlobalScopesTestModel; @@ -233,6 +242,12 @@ public static function boot() } } +#[ScopedBy(ActiveScope::class)] +class EloquentGlobalScopeInAttributeTestModel extends Model +{ + protected $table = 'table'; +} + class ActiveScope implements Scope { public function apply(Builder $builder, Model $model) From 7ff37c6403e831482666b149892e02162577f7a5 Mon Sep 17 00:00:00 2001 From: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Sun, 11 Feb 2024 22:31:24 +0400 Subject: [PATCH 096/325] Update reserved names in GeneratorCommand (#50043) Co-authored-by: Xurshudyan --- src/Illuminate/Console/GeneratorCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Console/GeneratorCommand.php b/src/Illuminate/Console/GeneratorCommand.php index efb349166b7f..1dc5ed792ee2 100644 --- a/src/Illuminate/Console/GeneratorCommand.php +++ b/src/Illuminate/Console/GeneratorCommand.php @@ -84,6 +84,7 @@ abstract class GeneratorCommand extends Command implements PromptsForMissingInpu 'namespace', 'new', 'or', + 'parent', 'print', 'private', 'protected', From 52305ed8a857b4e0e729c68455d60a608d1a75d3 Mon Sep 17 00:00:00 2001 From: Guillaume Stoehr Date: Mon, 12 Feb 2024 17:56:59 +0100 Subject: [PATCH 097/325] [10.x] fix Validator::validated get nullable array (#50056) * fix Validator::validated get nullable array * Update Validator.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Validation/Validator.php | 5 +++-- tests/Validation/ValidationValidatorTest.php | 22 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 35d18149af79..ed5028e322e4 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -584,14 +584,15 @@ public function validated() $missingValue = new stdClass; foreach ($this->getRules() as $key => $rules) { + $value = data_get($this->getData(), $key, $missingValue); + if ($this->excludeUnvalidatedArrayKeys && in_array('array', $rules) && + $value !== null && ! empty(preg_grep('/^'.preg_quote($key, '/').'\.+/', array_keys($this->getRules())))) { continue; } - $value = data_get($this->getData(), $key, $missingValue); - if ($value !== $missingValue) { Arr::set($results, $key, $value); } diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 7c9d7b31d3a2..bd3e5f834df2 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -293,6 +293,28 @@ public function testNullable() $this->assertSame('validation.boolean', $v->messages()->get('b')[0]); } + public function testArrayNullableWithUnvalidatedArrayKeys() + { + $trans = $this->getIlluminateArrayTranslator(); + + $v = new Validator($trans, [ + 'x' => null, + ], [ + 'x' => 'array|nullable', + 'x.key' => 'string', + ]); + $this->assertTrue($v->passes()); + $this->assertArrayHasKey('x', $v->validated()); + + $v = new Validator($trans, [ + 'x' => null, + ], [ + 'x' => 'array', + 'x.key' => 'string', + ]); + $this->assertFalse($v->passes()); + } + public function testNullableMakesNoDifferenceIfImplicitRuleExists() { $trans = $this->getIlluminateArrayTranslator(); From 67616d54566c49c0f5c681c6caba2ec2867330a3 Mon Sep 17 00:00:00 2001 From: Marcel Pociot Date: Tue, 13 Feb 2024 15:47:15 +0100 Subject: [PATCH 098/325] [10.x] Pass Herd specific env variables to "artisan serve" (#50069) * Add Herd specific env variables * Sort variables --- src/Illuminate/Foundation/Console/ServeCommand.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Illuminate/Foundation/Console/ServeCommand.php b/src/Illuminate/Foundation/Console/ServeCommand.php index 45ba5f9f3629..a73bf52ef5c1 100644 --- a/src/Illuminate/Foundation/Console/ServeCommand.php +++ b/src/Illuminate/Foundation/Console/ServeCommand.php @@ -57,6 +57,9 @@ class ServeCommand extends Command */ public static $passthroughVariables = [ 'APP_ENV', + 'HERD_PHP_81_INI_SCAN_DIR', + 'HERD_PHP_82_INI_SCAN_DIR', + 'HERD_PHP_83_INI_SCAN_DIR', 'IGNITION_LOCAL_SITES_PATH', 'LARAVEL_SAIL', 'PATH', From d3533e175d11ac8777503698f22f235a7ebbc187 Mon Sep 17 00:00:00 2001 From: MaximAL Date: Tue, 13 Feb 2024 17:48:28 +0300 Subject: [PATCH 099/325] Remove regex case insensitivity modifier in UUID detection to speed it up (#50067) --- src/Illuminate/Support/Str.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index b970d421d0f2..cbabea9fc9db 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -542,7 +542,7 @@ public static function isUuid($value) return false; } - return preg_match('/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iD', $value) > 0; + return preg_match('/^[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}$/D', $value) > 0; } /** From 9f255a7a10bbf067dd779349ba7e47aeae635fb9 Mon Sep 17 00:00:00 2001 From: Ahmed shamim Date: Tue, 13 Feb 2024 20:49:50 +0600 Subject: [PATCH 100/325] [10.x] HTTP retry method can accept array as first param (#50064) * HTTP retry method can accept array as first param * Update PendingRequest.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Http/Client/PendingRequest.php | 4 +-- tests/Http/HttpClientTest.php | 28 +++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 95d48e0f56bc..5dbb9dad8da2 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -600,13 +600,13 @@ public function connectTimeout(int $seconds) /** * Specify the number of times the request should be attempted. * - * @param int $times + * @param array|int $times * @param Closure|int $sleepMilliseconds * @param callable|null $when * @param bool $throw * @return $this */ - public function retry(int $times, Closure|int $sleepMilliseconds = 0, ?callable $when = null, bool $throw = true) + public function retry(array|int $times, Closure|int $sleepMilliseconds = 0, ?callable $when = null, bool $throw = true) { $this->tries = $times; $this->retryDelay = $sleepMilliseconds; diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 905c81469e06..9904e9165c01 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -1858,6 +1858,34 @@ public function testRequestsWillBeWaitingSleepMillisecondsReceivedBeforeRetry() ]); } + public function testRequestsWillBeWaitingSleepMillisecondsReceivedInBackoffArray() + { + Sleep::fake(); + + $this->factory->fake([ + '*' => $this->factory->sequence() + ->push(['error'], 500) + ->push(['error'], 500) + ->push(['error'], 500) + ->push(['ok'], 200), + ]); + + $this->factory + ->retry([50, 100, 200], 0, null, true) + ->get('http://foo.com/get'); + + $this->factory->assertSentCount(4); + + // Make sure we waited 300ms for the first two attempts + Sleep::assertSleptTimes(3); + + Sleep::assertSequence([ + Sleep::usleep(50_000), + Sleep::usleep(100_000), + Sleep::usleep(200_000), + ]); + } + public function testMiddlewareRunsWhenFaked() { $this->factory->fake(function (Request $request) { From 97e87b64c79813699eebb5145e8f1cc4bc605f4f Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Tue, 13 Feb 2024 14:50:39 +0000 Subject: [PATCH 101/325] Update facade docblocks --- src/Illuminate/Support/Facades/Http.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index ddd8e80b8fcb..c472d699e99c 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -53,7 +53,7 @@ * @method static \Illuminate\Http\Client\PendingRequest sink(string|resource $to) * @method static \Illuminate\Http\Client\PendingRequest timeout(int $seconds) * @method static \Illuminate\Http\Client\PendingRequest connectTimeout(int $seconds) - * @method static \Illuminate\Http\Client\PendingRequest retry(int $times, \Closure|int $sleepMilliseconds = 0, callable|null $when = null, bool $throw = true) + * @method static \Illuminate\Http\Client\PendingRequest retry(array|int $times, \Closure|int $sleepMilliseconds = 0, callable|null $when = null, bool $throw = true) * @method static \Illuminate\Http\Client\PendingRequest withOptions(array $options) * @method static \Illuminate\Http\Client\PendingRequest withMiddleware(callable $middleware) * @method static \Illuminate\Http\Client\PendingRequest withRequestMiddleware(callable $middleware) From af816e5b9d2e9963aafbd2a1081ecad0595aabc6 Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Tue, 13 Feb 2024 17:01:42 +0200 Subject: [PATCH 102/325] [10.x] Fix DB::afterCommit() broken in tests using DatabaseTransactions (#50068) * Add failing test case for test database transactions manager * Fix transaction commit in test causing all callbacks to execute in transaction --- .../Database/DatabaseTransactionsManager.php | 3 ++- .../Testing/DatabaseTransactionsManagerTest.php | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/DatabaseTransactionsManager.php b/src/Illuminate/Database/DatabaseTransactionsManager.php index c730dc503ac2..ee2889a2d18a 100755 --- a/src/Illuminate/Database/DatabaseTransactionsManager.php +++ b/src/Illuminate/Database/DatabaseTransactionsManager.php @@ -83,7 +83,8 @@ public function commit($connection, $levelBeingCommitted, $newTransactionLevel) // shouldn't be any pending transactions, but going to clear them here anyways just // in case. This method could be refactored to receive a level in the future too. $this->pendingTransactions = $this->pendingTransactions->reject( - fn ($transaction) => $transaction->connection === $connection + fn ($transaction) => $transaction->connection === $connection && + $transaction->level >= $levelBeingCommitted )->values(); [$forThisConnection, $forOtherConnections] = $this->committedTransactions->partition( diff --git a/tests/Foundation/Testing/DatabaseTransactionsManagerTest.php b/tests/Foundation/Testing/DatabaseTransactionsManagerTest.php index feac68b8e5bc..82ad8b410bee 100644 --- a/tests/Foundation/Testing/DatabaseTransactionsManagerTest.php +++ b/tests/Foundation/Testing/DatabaseTransactionsManagerTest.php @@ -31,6 +31,23 @@ public function testItIgnoresTheBaseTransactionForCallbackApplicableTransactions $this->assertEquals(2, $manager->callbackApplicableTransactions()[0]->level); } + public function testCommittingDoesNotRemoveTheBasePendingTransaction() + { + $manager = new DatabaseTransactionsManager; + + $manager->begin('foo', 1); + + $manager->begin('foo', 2); + $manager->commit('foo', 2, 1); + + $this->assertCount(0, $manager->callbackApplicableTransactions()); + + $manager->begin('foo', 2); + + $this->assertCount(1, $manager->callbackApplicableTransactions()); + $this->assertEquals(2, $manager->callbackApplicableTransactions()[0]->level); + } + public function testItExecutesCallbacksForTheSecondTransaction() { $testObject = new TestingDatabaseTransactionsManagerTestObject(); From 1199dbe361787bbe9648131a79f53921b4148cf6 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Tue, 13 Feb 2024 16:01:16 +0000 Subject: [PATCH 103/325] Update version to v10.44.0 --- 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 c1746bfab919..3cb07986767a 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.43.0'; + const VERSION = '10.44.0'; /** * The base path for the Laravel installation. From bf4f84be85d23d94c0d7f0f0148a1a0459ecff5b Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 13 Feb 2024 16:18:15 +0000 Subject: [PATCH 104/325] Update CHANGELOG --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eb175d5933c..033086916d46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,35 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.43.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.44.0...10.x) + +## [v10.44.0](https://github.com/laravel/framework/compare/v10.43.0...v10.44.0) - 2024-02-13 + +* [10.x] Fix empty request for HTTP connection exception by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/49924 +* [10.x] Add Collection::select() method by [@morrislaptop](https://github.com/morrislaptop) in https://github.com/laravel/framework/pull/49845 +* [10.x] Refactor `getPreviousUrlFromSession` method in UrlGenerator by [@milwad-dev](https://github.com/milwad-dev) in https://github.com/laravel/framework/pull/49944 +* [10.x] Add POSIX compliant cleanup to artisan serve by [@Tofandel](https://github.com/Tofandel) in https://github.com/laravel/framework/pull/49943 +* [10.x] Fix infinite loop when global scopes query contains aggregates by [@mateusjunges](https://github.com/mateusjunges) in https://github.com/laravel/framework/pull/49972 +* [10.x] Adds PHPUnit 11 as conflict by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/49957 +* Revert "[10.x] fix Before/After validation rules" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/50013 +* [10.x] Fix the phpdoc for replaceMatches in Str and Stringable helpers by [@joke2k](https://github.com/joke2k) in https://github.com/laravel/framework/pull/49990 +* [10.x] Added `setAbly()` method for `AblyBroadcaster` by [@Rijoanul-Shanto](https://github.com/Rijoanul-Shanto) in https://github.com/laravel/framework/pull/49981 +* [10.x] Fix in appendExceptionToException method exception type check by [@t1nkl](https://github.com/t1nkl) in https://github.com/laravel/framework/pull/49958 +* [10.x] DB command: add sqlcmd -C flag when 'trust_server_certificate' is set by [@hulkur](https://github.com/hulkur) in https://github.com/laravel/framework/pull/49952 +* Allows Setup and Teardown actions to be reused in alternative TestCase for Laravel by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/49973 +* [10.x] Add `toBase64()` and `fromBase64()` methods to Stringable and Str classes by [@mtownsend5512](https://github.com/mtownsend5512) in https://github.com/laravel/framework/pull/49984 +* [10.x] Allows to defer resolving pcntl only if it's available by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/50024 +* [10.x] Fixes missing `Throwable` import and handle if `originalExceptionHandler` or `originalDeprecationHandler` property isn't used by alternative TestCase by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/50021 +* [10.x] Type hinting for conditional validation rules by [@lorenzolosa](https://github.com/lorenzolosa) in https://github.com/laravel/framework/pull/50017 +* [10.x] Introduce new `Arr::take()` helper by [@ryangjchandler](https://github.com/ryangjchandler) in https://github.com/laravel/framework/pull/50015 +* [10.x] Improved Handling of Empty Component Slots with HTML Comments or Line Breaks by [@comes](https://github.com/comes) in https://github.com/laravel/framework/pull/49966 +* [10.x] Introduce Observe attribute for models by [@emargareten](https://github.com/emargareten) in https://github.com/laravel/framework/pull/49843 +* [10.x] Add ScopedBy attribute for models by [@emargareten](https://github.com/emargareten) in https://github.com/laravel/framework/pull/50034 +* [10.x] Update reserved names in `GeneratorCommand` by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/50043 +* [10.x] fix Validator::validated get nullable array by [@helitik](https://github.com/helitik) in https://github.com/laravel/framework/pull/50056 +* [10.x] Pass Herd specific env variables to "artisan serve" by [@mpociot](https://github.com/mpociot) in https://github.com/laravel/framework/pull/50069 +* Remove regex case insensitivity modifier in UUID detection to speed it up slightly by [@maximal](https://github.com/maximal) in https://github.com/laravel/framework/pull/50067 +* [10.x] HTTP retry method can accept array as first param by [@me-shaon](https://github.com/me-shaon) in https://github.com/laravel/framework/pull/50064 +* [10.x] Fix DB::afterCommit() broken in tests using DatabaseTransactions by [@oprypkhantc](https://github.com/oprypkhantc) in https://github.com/laravel/framework/pull/50068 ## [v10.43.0](https://github.com/laravel/framework/compare/v10.42.0...v10.43.0) - 2024-01-30 From 1a0c74aa69d3073f374d4de88a6a9886f26a6501 Mon Sep 17 00:00:00 2001 From: Milwad <98118400+milwad-dev@users.noreply.github.com> Date: Wed, 14 Feb 2024 01:22:37 +0330 Subject: [PATCH 105/325] update Stringable phpdoc (#50075) --- src/Illuminate/Support/Stringable.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index 6352bb62b200..fd344622cd3f 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -204,7 +204,7 @@ public function containsAll($needles, $ignoreCase = false) * Convert the case of a string. * * @param int $mode - * @param string $encoding + * @param string|null $encoding * @return static */ public function convertCase(int $mode = MB_CASE_FOLD, ?string $encoding = 'UTF-8') @@ -1249,7 +1249,7 @@ public function toHtmlString() /** * Convert the string to Base64 encoding. * - * @return void + * @return static */ public function toBase64() { @@ -1260,7 +1260,7 @@ public function toBase64() * Decode the Base64 encoded string. * * @param bool $strict - * @return void + * @return static */ public function fromBase64($strict = false) { From b7ca51d09164674b5eaa0d0ff4273234c279184c Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Tue, 13 Feb 2024 22:55:58 +0100 Subject: [PATCH 106/325] Allow `Collection::select()` to work on `ArrayAccess` (#50072) --- src/Illuminate/Collections/Arr.php | 12 +++- src/Illuminate/Collections/LazyCollection.php | 16 ++--- tests/Support/SupportCollectionTest.php | 64 ++++++++++++++++++- 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index f51accf1e408..291d3de39074 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -517,7 +517,17 @@ public static function only($array, $keys) public static function select($array, $keys) { return static::map($array, function ($item) use ($keys) { - return array_intersect_key($item, array_flip((array) $keys)); + $result = []; + + foreach ($keys as $key) { + if (Arr::accessible($item) && Arr::exists($item, $key)) { + $result[$key] = $item[$key]; + } elseif (is_object($item) && isset($item->{$key})) { + $result[$key] = $item->{$key}; + } + } + + return $result; }); } diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index b3fbb75c0c3d..a8b7716d35d6 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -972,19 +972,13 @@ public function select($keys) yield from $this; } else { foreach ($this as $item) { - $itemKeys = array_flip($keys); - $result = []; - foreach ($item as $key => $value) { - if (array_key_exists($key, $itemKeys)) { - $result[$key] = $value; - - unset($itemKeys[$key]); - - if (empty($itemKeys)) { - continue; - } + foreach ($keys as $key) { + if (Arr::accessible($item) && Arr::exists($item, $key)) { + $result[$key] = $item[$key]; + } elseif (is_object($item) && isset($item->{$key})) { + $result[$key] = $item->{$key}; } } diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index e6b02c8dcfed..15410edf4ddf 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -4231,7 +4231,7 @@ public function testOnly($collection) /** * @dataProvider collectionClassProvider */ - public function testSelect($collection) + public function testSelectWithArrays($collection) { $data = new $collection([ ['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com'], @@ -4259,6 +4259,68 @@ public function testSelect($collection) ], $data->select(collect(['first', 'email']))->all()); } + /** + * @dataProvider collectionClassProvider + */ + public function testSelectWithArrayAccess($collection) + { + $data = new $collection([ + new TestArrayAccessImplementation(['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com']), + new TestArrayAccessImplementation(['first' => 'Jess', 'last' => 'Archer', 'email' => 'jessarcher@gmail.com']), + ]); + + $this->assertEquals($data->all(), $data->select(null)->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select(['first', 'missing'])->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select('first', 'missing')->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select(collect(['first', 'missing']))->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select(['first', 'email'])->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select('first', 'email')->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select(collect(['first', 'email']))->all()); + } + + /** + * @dataProvider collectionClassProvider + */ + public function testSelectWithObjects($collection) + { + $data = new $collection([ + (object) ['first' => 'Taylor', 'last' => 'Otwell', 'email' => 'taylorotwell@gmail.com'], + (object) ['first' => 'Jess', 'last' => 'Archer', 'email' => 'jessarcher@gmail.com'], + ]); + + $this->assertEquals($data->all(), $data->select(null)->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select(['first', 'missing'])->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select('first', 'missing')->all()); + $this->assertEquals([['first' => 'Taylor'], ['first' => 'Jess']], $data->select(collect(['first', 'missing']))->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select(['first', 'email'])->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select('first', 'email')->all()); + + $this->assertEquals([ + ['first' => 'Taylor', 'email' => 'taylorotwell@gmail.com'], + ['first' => 'Jess', 'email' => 'jessarcher@gmail.com'], + ], $data->select(collect(['first', 'email']))->all()); + } + /** * @dataProvider collectionClassProvider */ From 84e7f85769ea268ffec9acab30f7a826223b3d6f Mon Sep 17 00:00:00 2001 From: Francisco Madeira Date: Tue, 13 Feb 2024 22:52:00 +0000 Subject: [PATCH 107/325] [10.x] Add `before` to the `PendingBatch` (#50058) * Add `before` to the `PendingBatch`. * style: fixes * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Bus/PendingBatch.php | 52 +++++++++++++++++++++++++++-- tests/Bus/BusPendingBatchTest.php | 38 ++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Bus/PendingBatch.php b/src/Illuminate/Bus/PendingBatch.php index b9622f8cc064..1b3b01bd061f 100644 --- a/src/Illuminate/Bus/PendingBatch.php +++ b/src/Illuminate/Bus/PendingBatch.php @@ -74,6 +74,31 @@ public function add($jobs) return $this; } + /** + * Add a callback to be executed when the batch is stored. + * + * @param callable $callback + * @return $this + */ + public function before($callback) + { + $this->options['before'][] = $callback instanceof Closure + ? new SerializableClosure($callback) + : $callback; + + return $this; + } + + /** + * Get the "before" callbacks that have been registered with the pending batch. + * + * @return array + */ + public function beforeCallbacks() + { + return $this->options['before'] ?? []; + } + /** * Add a callback to be executed after a job in the batch have executed successfully. * @@ -282,7 +307,7 @@ public function dispatch() $repository = $this->container->make(BatchRepository::class); try { - $batch = $repository->store($this); + $batch = $this->store($repository); $batch = $batch->add($this->jobs); } catch (Throwable $e) { @@ -309,7 +334,7 @@ public function dispatchAfterResponse() { $repository = $this->container->make(BatchRepository::class); - $batch = $repository->store($this); + $batch = $this->store($repository); if ($batch) { $this->container->terminating(function () use ($batch) { @@ -366,4 +391,27 @@ public function dispatchUnless($boolean) { return ! value($boolean) ? $this->dispatch() : null; } + + /** + * Store the batch using the given repository. + * + * @param \Illuminate\Bus\BatchRepository $repository + * @return \Illuminate\Bus\Batch + */ + protected function store($repository) + { + $batch = $repository->store($this); + + collect($this->beforeCallbacks())->each(function ($handler) use ($batch) { + try { + return $handler($batch); + } catch (Throwable $e) { + if (function_exists('report')) { + report($e); + } + } + }); + + return $batch; + } } diff --git a/tests/Bus/BusPendingBatchTest.php b/tests/Bus/BusPendingBatchTest.php index 6a6e9186e686..d5b9bd8d9ea7 100644 --- a/tests/Bus/BusPendingBatchTest.php +++ b/tests/Bus/BusPendingBatchTest.php @@ -37,7 +37,9 @@ public function test_pending_batch_may_be_configured_and_dispatched() $pendingBatch = new PendingBatch($container, new Collection([$job])); - $pendingBatch = $pendingBatch->progress(function () { + $pendingBatch = $pendingBatch->before(function () { + // + })->progress(function () { // })->then(function () { // @@ -47,6 +49,7 @@ public function test_pending_batch_may_be_configured_and_dispatched() $this->assertSame('test-connection', $pendingBatch->connection()); $this->assertSame('test-queue', $pendingBatch->queue()); + $this->assertCount(1, $pendingBatch->beforeCallbacks()); $this->assertCount(1, $pendingBatch->progressCallbacks()); $this->assertCount(1, $pendingBatch->thenCallbacks()); $this->assertCount(1, $pendingBatch->catchCallbacks()); @@ -186,4 +189,37 @@ public function test_batch_is_not_dispatched_when_dispatchunless_is_true() $this->assertNull($result); } + + public function test_batch_before_event_is_called() + { + $container = new Container; + + $eventDispatcher = m::mock(Dispatcher::class); + $eventDispatcher->shouldReceive('dispatch')->once(); + + $container->instance(Dispatcher::class, $eventDispatcher); + + $job = new class + { + use Batchable; + }; + + $beforeCalled = false; + + $pendingBatch = new PendingBatch($container, new Collection([$job])); + + $pendingBatch = $pendingBatch->before(function () use (&$beforeCalled) { + $beforeCalled = true; + })->onConnection('test-connection')->onQueue('test-queue'); + + $repository = m::mock(BatchRepository::class); + $repository->shouldReceive('store')->once()->with($pendingBatch)->andReturn($batch = m::mock(stdClass::class)); + $batch->shouldReceive('add')->once()->with(m::type(Collection::class))->andReturn($batch = m::mock(Batch::class)); + + $container->instance(BatchRepository::class, $repository); + + $pendingBatch->dispatch(); + + $this->assertTrue($beforeCalled); + } } From 0fc01c1a764e7a8b220242f96ca0963f38e9c8d6 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Wed, 14 Feb 2024 16:13:36 +0100 Subject: [PATCH 108/325] Adjust rules call sequence (#50084) --- src/Illuminate/Foundation/Http/FormRequest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Http/FormRequest.php b/src/Illuminate/Foundation/Http/FormRequest.php index 93e7d20026e0..4824c27c1799 100644 --- a/src/Illuminate/Foundation/Http/FormRequest.php +++ b/src/Illuminate/Foundation/Http/FormRequest.php @@ -115,9 +115,11 @@ protected function getValidatorInstance() */ protected function createDefaultValidator(ValidationFactory $factory) { + $rules = $this->validationRules(); + $validator = $factory->make( $this->validationData(), - $this->validationRules(), + $rules, $this->messages(), $this->attributes(), )->stopOnFirstFailure($this->stopOnFirstFailure); From 7784c1b8ac17bdd034395eb31e9a03bb6253848e Mon Sep 17 00:00:00 2001 From: Samson Endale <4sam21@gmail.com> Date: Fri, 16 Feb 2024 12:44:42 +0300 Subject: [PATCH 109/325] Minor typo (#50108) --- src/Illuminate/Support/Str.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index cbabea9fc9db..3eb1032a27f2 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1551,7 +1551,7 @@ public static function toBase64($string): string * * @param string $string * @param bool $strict - * @return void + * @return string */ public static function fromBase64($string, $strict = false) { From d5a35ef375cf0969e8d8e5cb0c948dee3471ee07 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Fri, 16 Feb 2024 09:59:36 +0000 Subject: [PATCH 110/325] Actually fix fromBase64 return type (#50113) --- src/Illuminate/Support/Str.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 3eb1032a27f2..9b576c6d7350 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1551,7 +1551,7 @@ public static function toBase64($string): string * * @param string $string * @param bool $strict - * @return string + * @return string|false */ public static function fromBase64($string, $strict = false) { From cf9513219e80608aded783e33a30f8e8d145f6c2 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 16 Feb 2024 11:04:27 +0100 Subject: [PATCH 111/325] Fix warning and deprecation (#50114) --- src/Illuminate/Support/Str.php | 4 ++++ tests/Support/SupportStrTest.php | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 9b576c6d7350..c866d7d2b39c 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1298,6 +1298,10 @@ public static function headline($value) */ public static function apa($value) { + if ($value === '') { + return $value; + } + $minorWords = [ 'and', 'as', 'but', 'for', 'if', 'nor', 'or', 'so', 'yet', 'a', 'an', 'the', 'at', 'by', 'for', 'in', 'of', 'off', 'on', 'per', 'to', 'up', 'via', diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index c9f6c0b0c63a..552d9efd2dbd 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -96,6 +96,8 @@ public function testStringApa() $this->assertSame('To Kill a Mockingbird', Str::apa('to kill a mockingbird')); $this->assertSame('To Kill a Mockingbird', Str::apa('TO KILL A MOCKINGBIRD')); $this->assertSame('To Kill a Mockingbird', Str::apa('To Kill A Mockingbird')); + + $this->assertSame('', Str::apa('')); } public function testStringWithoutWordsDoesntProduceError() From 85b28780401dfd37ca1230a30d39494ce642a0ea Mon Sep 17 00:00:00 2001 From: Dmytro Kulyk Date: Mon, 19 Feb 2024 04:05:20 +0200 Subject: [PATCH 112/325] [10.x] Mark model instanse as not exists on deleting MorphPivot relation. (#50135) * Mark model instanse as not exists on deleting MorphPivot relation. On deleted event, the model is marked as existing. Like in AsPivot::delete() * Update MorphPivot.php --- src/Illuminate/Database/Eloquent/Relations/MorphPivot.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php index 5ca8b48bed02..39c7852f2888 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphPivot.php @@ -68,6 +68,8 @@ public function delete() $query->where($this->morphType, $this->morphClass); return tap($query->delete(), function () { + $this->exists = false; + $this->fireModelEvent('deleted', false); }); } From 025b24cbc9f487ee88ca43f009123ef0c63b6b94 Mon Sep 17 00:00:00 2001 From: Italo Date: Mon, 19 Feb 2024 11:41:45 -0300 Subject: [PATCH 113/325] [10.x] Adds Tappable and Conditionable to Relation class (#50124) * [10.x] Fixes proxying conditionable and tappable to relation * [10.x] Fixes proxying conditionable and tappable to relation * Update Relation.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Eloquent/Relations/Relation.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/Relation.php b/src/Illuminate/Database/Eloquent/Relations/Relation.php index 7fea6b70d95b..af32aa6a7b0c 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Relation.php +++ b/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -10,12 +10,14 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\MultipleRecordsFoundException; use Illuminate\Database\Query\Expression; +use Illuminate\Support\Traits\Conditionable; +use Illuminate\Support\Traits\Tappable; use Illuminate\Support\Traits\ForwardsCalls; use Illuminate\Support\Traits\Macroable; abstract class Relation implements BuilderContract { - use ForwardsCalls, Macroable { + use Conditionable, ForwardsCalls, Macroable, Tappable { Macroable::__call as macroCall; } From 0804e5f3d1d806f41c0c39d211482f0586cfacf7 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 19 Feb 2024 14:42:06 +0000 Subject: [PATCH 114/325] Apply fixes from StyleCI --- src/Illuminate/Database/Eloquent/Relations/Relation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/Relation.php b/src/Illuminate/Database/Eloquent/Relations/Relation.php index af32aa6a7b0c..cffb14bf5a25 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Relation.php +++ b/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -11,9 +11,9 @@ use Illuminate\Database\MultipleRecordsFoundException; use Illuminate\Database\Query\Expression; use Illuminate\Support\Traits\Conditionable; -use Illuminate\Support\Traits\Tappable; use Illuminate\Support\Traits\ForwardsCalls; use Illuminate\Support\Traits\Macroable; +use Illuminate\Support\Traits\Tappable; abstract class Relation implements BuilderContract { From 4e19d4e8faf501cedef0bc83afbea1b207e5e375 Mon Sep 17 00:00:00 2001 From: Dmytro Kulyk Date: Tue, 20 Feb 2024 17:20:15 +0200 Subject: [PATCH 115/325] [10.x] Added getQualifiedMorphTypeName to MorphToMany (#50153) It is in addition to qualified names for MorphToMany relations. --- .../Database/Eloquent/Relations/MorphToMany.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php index 87b8e7816f9f..8cf113bd0f34 100644 --- a/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/MorphToMany.php @@ -189,6 +189,16 @@ public function getMorphType() return $this->morphType; } + /** + * Get the fully qualified morph type for the relation. + * + * @return string + */ + public function getQualifiedMorphTypeName() + { + return $this->qualifyPivotColumn($this->morphType); + } + /** * Get the class name of the parent model. * From 8b08d8cd79f8093eb51a8c59e21647bedfbf05f2 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Tue, 20 Feb 2024 15:32:48 +0000 Subject: [PATCH 116/325] Update version to v10.45.0 --- 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 3cb07986767a..a23f0a4a9272 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.44.0'; + const VERSION = '10.45.0'; /** * The base path for the Laravel installation. From 465668622caef586f047e86247984deedb893314 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 20 Feb 2024 16:10:57 +0000 Subject: [PATCH 117/325] Update CHANGELOG --- CHANGELOG.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 033086916d46..d20dd33a86fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,19 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.44.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.45.0...10.x) + +## [v10.45.0](https://github.com/laravel/framework/compare/v10.44.0...v10.45.0) - 2024-02-20 + +* [10.x] Update `Stringable` phpdoc by [@milwad-dev](https://github.com/milwad-dev) in https://github.com/laravel/framework/pull/50075 +* [10.x] Allow `Collection::select()` to work on `ArrayAccess` by [@axlon](https://github.com/axlon) in https://github.com/laravel/framework/pull/50072 +* [10.x] Add `before` to the `PendingBatch` by [@xiCO2k](https://github.com/xiCO2k) in https://github.com/laravel/framework/pull/50058 +* [10.x] Adjust rules call sequence by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50084 +* [10.x] Fixes `Illuminate\Support\Str::fromBase64()` return type by [@SamAsEnd](https://github.com/SamAsEnd) in https://github.com/laravel/framework/pull/50108 +* [10.x] Actually fix fromBase64 return type by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/50113 +* [10.x] Fix warning and deprecation for Str::api by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50114 +* [10.x] Mark model instanse as not exists on deleting MorphPivot relation. by [@dkulyk](https://github.com/dkulyk) in https://github.com/laravel/framework/pull/50135 +* [10.x] Adds Tappable and Conditionable to Relation class by [@DarkGhostHunter](https://github.com/DarkGhostHunter) in https://github.com/laravel/framework/pull/50124 +* [10.x] Added getQualifiedMorphTypeName to MorphToMany by [@dkulyk](https://github.com/dkulyk) in https://github.com/laravel/framework/pull/50153 ## [v10.44.0](https://github.com/laravel/framework/compare/v10.43.0...v10.44.0) - 2024-02-13 From 51134d6802b6193ec4b0a24c45a03b2bae730327 Mon Sep 17 00:00:00 2001 From: Kuba Szymanowski Date: Tue, 20 Feb 2024 18:03:56 +0100 Subject: [PATCH 118/325] Fix typehint for ResetPassword::toMailUsing() (#50163) --- src/Illuminate/Auth/Notifications/ResetPassword.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Auth/Notifications/ResetPassword.php b/src/Illuminate/Auth/Notifications/ResetPassword.php index 1d8da41bd1a8..efb4573e8be2 100644 --- a/src/Illuminate/Auth/Notifications/ResetPassword.php +++ b/src/Illuminate/Auth/Notifications/ResetPassword.php @@ -25,7 +25,7 @@ class ResetPassword extends Notification /** * The callback that should be used to build the mail message. * - * @var (\Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage)|null + * @var (\Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage|\Illuminate\Contracts\Mail\Mailable)|null */ public static $toMailCallback; @@ -114,7 +114,7 @@ public static function createUrlUsing($callback) /** * Set a callback that should be used when building the notification mail message. * - * @param \Closure(mixed, string): \Illuminate\Notifications\Messages\MailMessage $callback + * @param \Closure(mixed, string): (\Illuminate\Notifications\Messages\MailMessage|\Illuminate\Contracts\Mail\Mailable) $callback * @return void */ public static function toMailUsing($callback) From 08bf276de0efc9af130e10cb93ede827715543c3 Mon Sep 17 00:00:00 2001 From: Sjors Ottjes Date: Tue, 20 Feb 2024 21:17:36 +0100 Subject: [PATCH 119/325] fix Process::fake() not matching multi line commands (#50164) --- src/Illuminate/Process/PendingProcess.php | 2 +- tests/Process/ProcessTest.php | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Process/PendingProcess.php b/src/Illuminate/Process/PendingProcess.php index 320172bd266c..810ae6e6ed17 100644 --- a/src/Illuminate/Process/PendingProcess.php +++ b/src/Illuminate/Process/PendingProcess.php @@ -348,7 +348,7 @@ public function withFakeHandlers(array $fakeHandlers) protected function fakeFor(string $command) { return collect($this->fakeHandlers) - ->first(fn ($handler, $pattern) => Str::is($pattern, $command)); + ->first(fn ($handler, $pattern) => $pattern === '*' || Str::is($pattern, $command)); } /** diff --git a/tests/Process/ProcessTest.php b/tests/Process/ProcessTest.php index 9908b1a76de3..4557004e1d1a 100644 --- a/tests/Process/ProcessTest.php +++ b/tests/Process/ProcessTest.php @@ -148,6 +148,26 @@ public function testBasicProcessFake() $this->assertTrue($result->successful()); } + public function testBasicProcessFakeWithMultiLineCommand() + { + $factory = new Factory; + + $factory->preventStrayProcesses(); + + $factory->fake([ + '*' => 'The output', + ]); + + $result = $factory->run(<<<'COMMAND' + git clone --depth 1 \ + --single-branch \ + --branch main \ + git://some-url . + COMMAND); + + $this->assertSame(0, $result->exitCode()); + } + public function testProcessFakeExitCodes() { $factory = new Factory; From 6b98a716f9f66cca9ed43a3edb8fc3fd18997ec3 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 21 Feb 2024 08:07:01 -0600 Subject: [PATCH 120/325] wip --- src/Illuminate/Database/Eloquent/Relations/Relation.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/Relation.php b/src/Illuminate/Database/Eloquent/Relations/Relation.php index cffb14bf5a25..7fea6b70d95b 100755 --- a/src/Illuminate/Database/Eloquent/Relations/Relation.php +++ b/src/Illuminate/Database/Eloquent/Relations/Relation.php @@ -10,14 +10,12 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\MultipleRecordsFoundException; use Illuminate\Database\Query\Expression; -use Illuminate\Support\Traits\Conditionable; use Illuminate\Support\Traits\ForwardsCalls; use Illuminate\Support\Traits\Macroable; -use Illuminate\Support\Traits\Tappable; abstract class Relation implements BuilderContract { - use Conditionable, ForwardsCalls, Macroable, Tappable { + use ForwardsCalls, Macroable { Macroable::__call as macroCall; } From dcf5d1d722b84ad38a5e053289130b6962f830bd Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 21 Feb 2024 14:07:36 +0000 Subject: [PATCH 121/325] Update version to v10.45.1 --- 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 a23f0a4a9272..ea5cbe2ca532 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.45.0'; + const VERSION = '10.45.1'; /** * The base path for the Laravel installation. From abeec173e027cde01f9cd1ac5ca1c485cfbcf20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20N=C3=BCrnberger?= Date: Wed, 21 Feb 2024 15:12:51 +0100 Subject: [PATCH 122/325] add regression test (#50176) --- .../Database/EloquentMorphEagerLoadingTest.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/Integration/Database/EloquentMorphEagerLoadingTest.php b/tests/Integration/Database/EloquentMorphEagerLoadingTest.php index c251f0c105c7..8f6d4405bd63 100644 --- a/tests/Integration/Database/EloquentMorphEagerLoadingTest.php +++ b/tests/Integration/Database/EloquentMorphEagerLoadingTest.php @@ -95,6 +95,21 @@ public function testMorphLoadingMixedWithTrashedRelations() $this->assertTrue($action[1]->relationLoaded('target')); $this->assertInstanceOf(User::class, $action[1]->getRelation('target')); } + + public function testMorphWithTrashedRelationLazyLoading() + { + $deletedUser = User::forceCreate(['deleted_at' => now()]); + + $action = new Action; + $action->target()->associate($deletedUser)->save(); + + // model is already set via associate and not retrieved from the database + $this->assertInstanceOf(User::class, $action->target); + + $action->unsetRelation('target'); + + $this->assertInstanceOf(User::class, $action->target); + } } class Action extends Model From e708043384ab9887cfcb9aa20453f54c0d888855 Mon Sep 17 00:00:00 2001 From: Brenier Arnaud Date: Wed, 21 Feb 2024 15:17:53 +0100 Subject: [PATCH 123/325] [10.x] Arr::select not working when $keys is a string (#50169) * Arr::select not working when $keys is a string Adding a check about $keys type. If it is a string, transform it into an array. If not, it throws a foreach() error * add test --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/Arr.php | 2 ++ tests/Support/SupportArrTest.php | 36 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index 291d3de39074..e75d87ae979e 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -516,6 +516,8 @@ public static function only($array, $keys) */ public static function select($array, $keys) { + $keys = static::wrap($keys); + return static::map($array, function ($item) use ($keys) { $result = []; diff --git a/tests/Support/SupportArrTest.php b/tests/Support/SupportArrTest.php index 05f6b743f3e1..119dd2a3bd65 100644 --- a/tests/Support/SupportArrTest.php +++ b/tests/Support/SupportArrTest.php @@ -1273,4 +1273,40 @@ public function testTake() 4, 5, 6, ], Arr::take($array, -3)); } + + public function testSelect() + { + $array = [ + [ + 'name' => 'Taylor', + 'role' => 'Developer', + 'age' => 1, + ], + [ + 'name' => 'Abigail', + 'role' => 'Infrastructure', + 'age' => 2, + ], + ]; + + $this->assertEquals([ + [ + 'name' => 'Taylor', + 'age' => 1, + ], + [ + 'name' => 'Abigail', + 'age' => 2, + ], + ], Arr::select($array, ['name', 'age'])); + + $this->assertEquals([ + [ + 'name' => 'Taylor', + ], + [ + 'name' => 'Abigail', + ], + ], Arr::select($array, 'name')); + } } From 500fb9fffcaecd7d8972e5e1f2a6023da7f578ac Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Wed, 21 Feb 2024 14:18:14 +0000 Subject: [PATCH 124/325] Apply fixes from StyleCI --- src/Illuminate/Collections/Arr.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Arr.php b/src/Illuminate/Collections/Arr.php index e75d87ae979e..361808418ffa 100644 --- a/src/Illuminate/Collections/Arr.php +++ b/src/Illuminate/Collections/Arr.php @@ -517,7 +517,7 @@ public static function only($array, $keys) public static function select($array, $keys) { $keys = static::wrap($keys); - + return static::map($array, function ($item) use ($keys) { $result = []; From f7c57c47f677b3fcfa1c70c6c3c51d0f90866d31 Mon Sep 17 00:00:00 2001 From: Dmytro Kulyk Date: Wed, 21 Feb 2024 17:19:17 +0200 Subject: [PATCH 125/325] Added passing loaded relationship to value callback (#50167) --- .../Http/Resources/ConditionallyLoadsAttributes.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php b/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php index 9940c3e0cea6..0f03ebda832b 100644 --- a/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php +++ b/src/Illuminate/Http/Resources/ConditionallyLoadsAttributes.php @@ -266,15 +266,17 @@ protected function whenLoaded($relationship, $value = null, $default = null) return value($default); } + $loadedValue = $this->resource->{$relationship}; + if (func_num_args() === 1) { - return $this->resource->{$relationship}; + return $loadedValue; } - if ($this->resource->{$relationship} === null) { + if ($loadedValue === null) { return; } - return value($value); + return value($value, $loadedValue); } /** From e550f2bf06e4d2f44bf241d2861eea059904976f Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Wed, 21 Feb 2024 19:58:47 +0000 Subject: [PATCH 126/325] [10.x] Fix optional charset and collation when creating database (#50168) * Fix optional charset and collation when creating database * Update MySqlGrammar.php * Re-use the var i made for this --- .../Database/Schema/Grammars/MySqlGrammar.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index d54e9a6fe218..26d97a7a5541 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -43,11 +43,21 @@ class MySqlGrammar extends Grammar */ public function compileCreateDatabase($name, $connection) { + $charset = $connection->getConfig('charset'); + $collation = $connection->getConfig('collation'); + + if (! $charset || ! $collation) { + return sprintf( + 'create database %s', + $this->wrapValue($name), + ); + } + return sprintf( 'create database %s default character set %s default collate %s', $this->wrapValue($name), - $this->wrapValue($connection->getConfig('charset')), - $this->wrapValue($connection->getConfig('collation')), + $this->wrapValue($charset), + $this->wrapValue($collation), ); } From ae606ae6004c1ae7dca922fe37ab3f9e517c197f Mon Sep 17 00:00:00 2001 From: "S.a Mahmoudzadeh" <36761585+saMahmoudzadeh@users.noreply.github.com> Date: Thu, 22 Feb 2024 18:22:10 +0330 Subject: [PATCH 127/325] [10.x] update doc block in PendingProcess.php (#50198) * refactor(PendingProcess): update doc block for start method * refactor(PendingProcess): update doc block for resolveAsynchronousFake method --- src/Illuminate/Process/PendingProcess.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Process/PendingProcess.php b/src/Illuminate/Process/PendingProcess.php index 810ae6e6ed17..53ffdd130def 100644 --- a/src/Illuminate/Process/PendingProcess.php +++ b/src/Illuminate/Process/PendingProcess.php @@ -266,8 +266,10 @@ public function run(array|string $command = null, callable $output = null) * Start the process in the background. * * @param array|string|null $command - * @param callable $output + * @param callable|null $output * @return \Illuminate\Process\InvokedProcess + * + * @throws \RuntimeException */ public function start(array|string $command = null, callable $output = null) { @@ -382,6 +384,8 @@ protected function resolveSynchronousFake(string $command, Closure $fake) * @param callable|null $output * @param \Closure $fake * @return \Illuminate\Process\FakeInvokedProcess + * + * @throw \LogicException */ protected function resolveAsynchronousFake(string $command, ?callable $output, Closure $fake) { From e0be50a7b770c46b522377cb46318e06e312014e Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Thu, 22 Feb 2024 14:52:40 +0000 Subject: [PATCH 128/325] Update facade docblocks --- src/Illuminate/Support/Facades/Process.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Process.php b/src/Illuminate/Support/Facades/Process.php index 1df17ba7e7a9..43b5b93a6578 100644 --- a/src/Illuminate/Support/Facades/Process.php +++ b/src/Illuminate/Support/Facades/Process.php @@ -17,7 +17,7 @@ * @method static \Illuminate\Process\PendingProcess tty(bool $tty = true) * @method static \Illuminate\Process\PendingProcess options(array $options) * @method static \Illuminate\Contracts\Process\ProcessResult run(array|string|null $command = null, callable|null $output = null) - * @method static \Illuminate\Process\InvokedProcess start(array|string|null $command = null, callable $output = null) + * @method static \Illuminate\Process\InvokedProcess start(array|string|null $command = null, callable|null $output = null) * @method static \Illuminate\Process\PendingProcess withFakeHandlers(array $fakeHandlers) * @method static \Illuminate\Process\PendingProcess|mixed when(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) * @method static \Illuminate\Process\PendingProcess|mixed unless(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) From 4ebce1e453d6f2c5c23905751f781ec2a6edf0b6 Mon Sep 17 00:00:00 2001 From: "S.a Mahmoudzadeh" <36761585+saMahmoudzadeh@users.noreply.github.com> Date: Fri, 23 Feb 2024 19:08:25 +0330 Subject: [PATCH 129/325] [10.x] Fix Accepting nullable Parameters, updated doc block, and null pointer exception handling in batchable trait (#50209) * fix(Reachable Trait): fix parameters type and doc block in With withFakeBatch method * fix(Reachable Trait): fix null pointer exception * fix(Batchable Trait): fix parameters type and doc block in With withFakeBatch method * fix(Batchable Trait): fix null pointer exception * Update Batchable.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Bus/Batchable.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Bus/Batchable.php b/src/Illuminate/Bus/Batchable.php index 0b082700f8a2..5cf5706070e9 100644 --- a/src/Illuminate/Bus/Batchable.php +++ b/src/Illuminate/Bus/Batchable.php @@ -35,7 +35,7 @@ public function batch() } if ($this->batchId) { - return Container::getInstance()->make(BatchRepository::class)->find($this->batchId); + return Container::getInstance()->make(BatchRepository::class)?->find($this->batchId); } } @@ -74,7 +74,7 @@ public function withBatchId(string $batchId) * @param int $failedJobs * @param array $failedJobIds * @param array $options - * @param \Carbon\CarbonImmutable $createdAt + * @param \Carbon\CarbonImmutable|null $createdAt * @param \Carbon\CarbonImmutable|null $cancelledAt * @param \Carbon\CarbonImmutable|null $finishedAt * @return array{0: $this, 1: \Illuminate\Support\Testing\Fakes\BatchFake} @@ -86,7 +86,7 @@ public function withFakeBatch(string $id = '', int $failedJobs = 0, array $failedJobIds = [], array $options = [], - CarbonImmutable $createdAt = null, + ?CarbonImmutable $createdAt = null, ?CarbonImmutable $cancelledAt = null, ?CarbonImmutable $finishedAt = null) { From dda69ba7208a3093390594ba2f6095041d2b18c2 Mon Sep 17 00:00:00 2001 From: driesvints Date: Fri, 23 Feb 2024 16:14:41 +0000 Subject: [PATCH 130/325] Update CHANGELOG --- CHANGELOG.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d20dd33a86fe..c48b7d1f94ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.45.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.45.1...10.x) + +## [v10.45.1](https://github.com/laravel/framework/compare/v10.45.0...v10.45.1) - 2024-02-21 + +### What's Changed + +* Fix typehint for ResetPassword::toMailUsing() by [@KKSzymanowski](https://github.com/KKSzymanowski) in https://github.com/laravel/framework/pull/50163 +* [10.x] Fix Process::fake() never matching multi-line commands by [@SjorsO](https://github.com/SjorsO) in https://github.com/laravel/framework/pull/50164 + +**Full Changelog**: https://github.com/laravel/framework/compare/v10.45.0...v10.45.1 ## [v10.45.0](https://github.com/laravel/framework/compare/v10.44.0...v10.45.0) - 2024-02-20 From d309d71f0102719be7e7984e4462889d2ab24f48 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 23 Feb 2024 17:16:58 +0100 Subject: [PATCH 131/325] Update CHANGELOG.md --- CHANGELOG.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c48b7d1f94ab..6ba419c3be85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,9 @@ ## [v10.45.1](https://github.com/laravel/framework/compare/v10.45.0...v10.45.1) - 2024-02-21 -### What's Changed - * Fix typehint for ResetPassword::toMailUsing() by [@KKSzymanowski](https://github.com/KKSzymanowski) in https://github.com/laravel/framework/pull/50163 * [10.x] Fix Process::fake() never matching multi-line commands by [@SjorsO](https://github.com/SjorsO) in https://github.com/laravel/framework/pull/50164 -**Full Changelog**: https://github.com/laravel/framework/compare/v10.45.0...v10.45.1 - ## [v10.45.0](https://github.com/laravel/framework/compare/v10.44.0...v10.45.0) - 2024-02-20 * [10.x] Update `Stringable` phpdoc by [@milwad-dev](https://github.com/milwad-dev) in https://github.com/laravel/framework/pull/50075 From 56250738b43f0ff3e20a3596ac2caa0b589a1aac Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 23 Feb 2024 17:33:02 +0100 Subject: [PATCH 132/325] Fix release notes --- .github/workflows/releases.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 43c7f2611a4a..e6177b2222a8 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -51,8 +51,8 @@ jobs: - name: Cleanup release notes run: | - sed -i '/## What'"'"'s Changed/d' ${{ steps.notes.outputs.release-notes }} - sed -i '/## New Contributors/,//d' ${{ steps.notes.outputs.release-notes }} + sed -i '/## What/d' ${{ steps.notes.outputs.release-notes }} + sed -i '/## New Contributors/,$d' ${{ steps.notes.outputs.release-notes }} - name: Create release uses: softprops/action-gh-release@v1 From 30324cf06d1f34fe359c5e458bcbb8da5413db71 Mon Sep 17 00:00:00 2001 From: Liam Duckett <116881406+liamduckett@users.noreply.github.com> Date: Sun, 25 Feb 2024 14:18:43 +0000 Subject: [PATCH 133/325] Make GuardsAttributes fillable property DocBlock more specific (#50229) --- src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php index b7e0d7dea8c0..f7d4c9ff538d 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/GuardsAttributes.php @@ -7,7 +7,7 @@ trait GuardsAttributes /** * The attributes that are mass assignable. * - * @var array + * @var array */ protected $fillable = []; From 8d47be393e43ffeacd49556471110454f868da5f Mon Sep 17 00:00:00 2001 From: Anton5360 <72033639+Anton5360@users.noreply.github.com> Date: Sun, 25 Feb 2024 07:30:59 -0700 Subject: [PATCH 134/325] [10.x] Add only and except methods to Enum validation rule (#50226) * Implement only and except logic * Cover only and except logic with tests * Fix code styling * Fix code styling * Improve php doc * Fix code styling * Fix code styling * formatting * fix visibility * fix type hints * remove type hint * fix type hints --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Validation/Rules/Enum.php | 62 ++++++++++++++- tests/Validation/ValidationEnumRuleTest.php | 88 +++++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Validation/Rules/Enum.php b/src/Illuminate/Validation/Rules/Enum.php index d66a16d126bc..42d9e6f012d2 100644 --- a/src/Illuminate/Validation/Rules/Enum.php +++ b/src/Illuminate/Validation/Rules/Enum.php @@ -4,6 +4,7 @@ use Illuminate\Contracts\Validation\Rule; use Illuminate\Contracts\Validation\ValidatorAwareRule; +use Illuminate\Support\Arr; use TypeError; class Enum implements Rule, ValidatorAwareRule @@ -22,6 +23,20 @@ class Enum implements Rule, ValidatorAwareRule */ protected $validator; + /** + * The cases that should be considered valid. + * + * @var array + */ + protected $only = []; + + /** + * The cases that should be considered invalid. + * + * @var array + */ + protected $except = []; + /** * Create a new rule instance. * @@ -43,7 +58,7 @@ public function __construct($type) public function passes($attribute, $value) { if ($value instanceof $this->type) { - return true; + return $this->isDesirable($value); } if (is_null($value) || ! enum_exists($this->type) || ! method_exists($this->type, 'tryFrom')) { @@ -51,12 +66,55 @@ public function passes($attribute, $value) } try { - return ! is_null($this->type::tryFrom($value)); + $value = $this->type::tryFrom($value); + + return ! is_null($value) && $this->isDesirable($value); } catch (TypeError) { return false; } } + /** + * Specify the cases that should be considered valid. + * + * @param \UnitEnum[]|\UnitEnum $values + * @return $this + */ + public function only($values) + { + $this->only = Arr::wrap($values); + + return $this; + } + + /** + * Specify the cases that should be considered invalid. + * + * @param \UnitEnum[]|\UnitEnum $values + * @return $this + */ + public function except($values) + { + $this->except = Arr::wrap($values); + + return $this; + } + + /** + * Determine if the given case is a valid case based on the only / except values. + * + * @param mixed $value + * @return bool + */ + protected function isDesirable($value) + { + return match (true) { + ! empty($this->only) => in_array(needle: $value, haystack: $this->only, strict: true), + ! empty($this->except) => ! in_array(needle: $value, haystack: $this->except, strict: true), + default => true, + }; + } + /** * Get the validation error message. * diff --git a/tests/Validation/ValidationEnumRuleTest.php b/tests/Validation/ValidationEnumRuleTest.php index 1c14c766f262..beffc1b314b1 100644 --- a/tests/Validation/ValidationEnumRuleTest.php +++ b/tests/Validation/ValidationEnumRuleTest.php @@ -78,6 +78,84 @@ public function testValidationFailsWhenProvidingNoExistingCases() $this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status')); } + public function testValidationPassesForAllCasesUntilEitherOnlyOrExceptIsPassed() + { + $v = new Validator( + resolve('translator'), + [ + 'status_1' => PureEnum::one, + 'status_2' => PureEnum::two, + 'status_3' => IntegerStatus::done->value, + ], + [ + 'status_1' => new Enum(PureEnum::class), + 'status_2' => (new Enum(PureEnum::class))->only([])->except([]), + 'status_3' => new Enum(IntegerStatus::class), + ], + ); + + $this->assertTrue($v->passes()); + } + + /** + * @dataProvider conditionalCasesDataProvider + */ + public function testValidationPassesWhenOnlyCasesProvided( + IntegerStatus|int $enum, + array|IntegerStatus $only, + bool $expected + ) { + $v = new Validator( + resolve('translator'), + [ + 'status' => $enum, + ], + [ + 'status' => (new Enum(IntegerStatus::class))->only($only), + ], + ); + + $this->assertSame($expected, $v->passes()); + } + + /** + * @dataProvider conditionalCasesDataProvider + */ + public function testValidationPassesWhenExceptCasesProvided( + int|IntegerStatus $enum, + array|IntegerStatus $except, + bool $expected + ) { + $v = new Validator( + resolve('translator'), + [ + 'status' => $enum, + ], + [ + 'status' => (new Enum(IntegerStatus::class))->except($except), + ], + ); + + $this->assertSame($expected, $v->fails()); + } + + public function testOnlyHasHigherOrderThanExcept() + { + $v = new Validator( + resolve('translator'), + [ + 'status' => PureEnum::one, + ], + [ + 'status' => (new Enum(PureEnum::class)) + ->only(PureEnum::one) + ->except(PureEnum::one), + ], + ); + + $this->assertTrue($v->passes()); + } + public function testValidationFailsWhenProvidingDifferentType() { $v = new Validator( @@ -171,6 +249,16 @@ public function testValidationFailsWhenProvidingStringToIntegerType() $this->assertEquals(['The selected status is invalid.'], $v->messages()->get('status')); } + public static function conditionalCasesDataProvider(): array + { + return [ + [IntegerStatus::done, IntegerStatus::done, true], + [IntegerStatus::done, [IntegerStatus::done, IntegerStatus::pending], true], + [IntegerStatus::pending->value, [IntegerStatus::done, IntegerStatus::pending], true], + [IntegerStatus::done->value, IntegerStatus::pending, false], + ]; + } + protected function setUp(): void { $container = Container::getInstance(); From 7af199446137b8eaba53a3bea2edd028279c6176 Mon Sep 17 00:00:00 2001 From: Guilhem-DELAITRE <89917125+Guilhem-DELAITRE@users.noreply.github.com> Date: Sun, 25 Feb 2024 15:40:35 +0100 Subject: [PATCH 135/325] [10.x] Fixes on nesting operations performed while applying scopes. (#50207) * Add tests to emphasize the issues with nesting due to scope (nesting "or" groups but not "or not" groups and doubling first where clause negation) * Fix issues : also nest on "or not" clause, and don't repeat first where clause negation when nesting --- src/Illuminate/Database/Eloquent/Builder.php | 4 +-- .../DatabaseEloquentLocalScopesTest.php | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 5563b5477a2b..185781580684 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -1454,9 +1454,9 @@ protected function groupWhereSliceForScope(QueryBuilder $query, $whereSlice) // Here we'll check if the given subset of where clauses contains any "or" // booleans and in this case create a nested where expression. That way // we don't add any unnecessary nesting thus keeping the query clean. - if ($whereBooleans->contains('or')) { + if ($whereBooleans->contains(fn ($logicalOperator) => str_contains($logicalOperator, 'or'))) { $query->wheres[] = $this->createNestedWhere( - $whereSlice, $whereBooleans->first() + $whereSlice, str_replace(' not', '', $whereBooleans->first()) ); } else { $query->wheres = array_merge($query->wheres, $whereSlice); diff --git a/tests/Database/DatabaseEloquentLocalScopesTest.php b/tests/Database/DatabaseEloquentLocalScopesTest.php index 1d71f6f57661..d34a510f1e5f 100644 --- a/tests/Database/DatabaseEloquentLocalScopesTest.php +++ b/tests/Database/DatabaseEloquentLocalScopesTest.php @@ -61,6 +61,32 @@ public function testLocalScopesCanChained() $this->assertSame('select * from "table" where "active" = ? and "type" = ?', $query->toSql()); $this->assertEquals([true, 'foo'], $query->getBindings()); } + + public function testLocalScopeNestingDoesntDoubleFirstWhereClauseNegation() + { + $model = new EloquentLocalScopesTestModel; + $query = $model + ->newQuery() + ->whereNot('firstWhere', true) + ->orWhere('secondWhere', true) + ->active(); + + $this->assertSame('select * from "table" where (not "firstWhere" = ? or "secondWhere" = ?) and "active" = ?', $query->toSql()); + $this->assertEquals([true, true, true], $query->getBindings()); + } + + public function testLocalScopeNestingGroupsOrNotWhereClause() + { + $model = new EloquentLocalScopesTestModel; + $query = $model + ->newQuery() + ->where('firstWhere', true) + ->orWhereNot('secondWhere', true) + ->active(); + + $this->assertSame('select * from "table" where ("firstWhere" = ? or not "secondWhere" = ?) and "active" = ?', $query->toSql()); + $this->assertEquals([true, true, true], $query->getBindings()); + } } class EloquentLocalScopesTestModel extends Model From e45d4248840b5e43d20eec0c7db448e68f386130 Mon Sep 17 00:00:00 2001 From: Sebastien Armand Date: Sun, 25 Feb 2024 06:45:05 -0800 Subject: [PATCH 136/325] [10.x] Custom RateLimiter increase (#50197) * Allow ratelimiter custom increments * add test * formatting * formatting * Revert formatting * Formatting * formatting --------- Co-authored-by: Tim MacDonald Co-authored-by: Taylor Otwell --- src/Illuminate/Cache/RateLimiter.php | 17 +++++++++++++++-- tests/Cache/CacheRateLimiterTest.php | 19 +++++++++++++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Cache/RateLimiter.php b/src/Illuminate/Cache/RateLimiter.php index 5f5fac0659b6..afdb9b25a208 100644 --- a/src/Illuminate/Cache/RateLimiter.php +++ b/src/Illuminate/Cache/RateLimiter.php @@ -105,13 +105,26 @@ public function tooManyAttempts($key, $maxAttempts) } /** - * Increment the counter for a given key for a given decay time. + * Increment (by 1) the counter for a given key for a given decay time. * * @param string $key * @param int $decaySeconds * @return int */ public function hit($key, $decaySeconds = 60) + { + return $this->increment($key, $decaySeconds); + } + + /** + * Increment the counter for a given key for a given decay time by a given amount. + * + * @param string $key + * @param int $decaySeconds + * @param int $amount + * @return int + */ + public function increment($key, $decaySeconds = 60, $amount = 1) { $key = $this->cleanRateLimiterKey($key); @@ -121,7 +134,7 @@ public function hit($key, $decaySeconds = 60) $added = $this->cache->add($key, 0, $decaySeconds); - $hits = (int) $this->cache->increment($key); + $hits = (int) $this->cache->increment($key, $amount); if (! $added && $hits == 1) { $this->cache->put($key, 1, $decaySeconds); diff --git a/tests/Cache/CacheRateLimiterTest.php b/tests/Cache/CacheRateLimiterTest.php index 660179256c84..0805f92af092 100644 --- a/tests/Cache/CacheRateLimiterTest.php +++ b/tests/Cache/CacheRateLimiterTest.php @@ -30,18 +30,29 @@ public function testHitProperlyIncrementsAttemptCount() $cache = m::mock(Cache::class); $cache->shouldReceive('add')->once()->with('key:timer', m::type('int'), 1)->andReturn(true); $cache->shouldReceive('add')->once()->with('key', 0, 1)->andReturn(true); - $cache->shouldReceive('increment')->once()->with('key')->andReturn(1); + $cache->shouldReceive('increment')->once()->with('key', 1)->andReturn(1); $rateLimiter = new RateLimiter($cache); $rateLimiter->hit('key', 1); } + public function testIncrementProperlyIncrementsAttemptCount() + { + $cache = m::mock(Cache::class); + $cache->shouldReceive('add')->once()->with('key:timer', m::type('int'), 1)->andReturn(true); + $cache->shouldReceive('add')->once()->with('key', 0, 1)->andReturn(true); + $cache->shouldReceive('increment')->once()->with('key', 5)->andReturn(5); + $rateLimiter = new RateLimiter($cache); + + $rateLimiter->increment('key', 1, 5); + } + public function testHitHasNoMemoryLeak() { $cache = m::mock(Cache::class); $cache->shouldReceive('add')->once()->with('key:timer', m::type('int'), 1)->andReturn(true); $cache->shouldReceive('add')->once()->with('key', 0, 1)->andReturn(false); - $cache->shouldReceive('increment')->once()->with('key')->andReturn(1); + $cache->shouldReceive('increment')->once()->with('key', 1)->andReturn(1); $cache->shouldReceive('put')->once()->with('key', 1, 1); $rateLimiter = new RateLimiter($cache); @@ -83,7 +94,7 @@ public function testAttemptsCallbackReturnsTrue() $cache->shouldReceive('get')->once()->with('key', 0)->andReturn(0); $cache->shouldReceive('add')->once()->with('key:timer', m::type('int'), 1); $cache->shouldReceive('add')->once()->with('key', 0, 1)->andReturns(1); - $cache->shouldReceive('increment')->once()->with('key')->andReturn(1); + $cache->shouldReceive('increment')->once()->with('key', 1)->andReturn(1); $executed = false; @@ -101,7 +112,7 @@ public function testAttemptsCallbackReturnsCallbackReturn() $cache->shouldReceive('get')->times(6)->with('key', 0)->andReturn(0); $cache->shouldReceive('add')->times(6)->with('key:timer', m::type('int'), 1); $cache->shouldReceive('add')->times(6)->with('key', 0, 1)->andReturns(1); - $cache->shouldReceive('increment')->times(6)->with('key')->andReturn(1); + $cache->shouldReceive('increment')->times(6)->with('key', 1)->andReturn(1); $rateLimiter = new RateLimiter($cache); From ca2ce7c0924c325590b7506b3d830f5b845ab9a8 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Sun, 25 Feb 2024 14:45:37 +0000 Subject: [PATCH 137/325] Update facade docblocks --- src/Illuminate/Support/Facades/RateLimiter.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/RateLimiter.php b/src/Illuminate/Support/Facades/RateLimiter.php index 5ac2e5b36dd0..e8b3ab3fe4f5 100644 --- a/src/Illuminate/Support/Facades/RateLimiter.php +++ b/src/Illuminate/Support/Facades/RateLimiter.php @@ -8,6 +8,7 @@ * @method static mixed attempt(string $key, int $maxAttempts, \Closure $callback, int $decaySeconds = 60) * @method static bool tooManyAttempts(string $key, int $maxAttempts) * @method static int hit(string $key, int $decaySeconds = 60) + * @method static int increment(string $key, int $decaySeconds = 60, int $amount = 1) * @method static mixed attempts(string $key) * @method static mixed resetAttempts(string $key) * @method static int remaining(string $key, int $maxAttempts) From 5f8684b3dde621a3fd6b523e9f48f232de38611c Mon Sep 17 00:00:00 2001 From: Magnus Hauge Bakke Date: Sun, 25 Feb 2024 16:34:09 +0100 Subject: [PATCH 138/325] [10.x] Add Lateral Join to Query Builder (#50050) * Add lateral join support to Query Builder * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Query/Builder.php | 46 +++++++ .../Database/Query/Grammars/Grammar.php | 19 +++ .../Database/Query/Grammars/MySqlGrammar.php | 13 ++ .../Query/Grammars/PostgresGrammar.php | 13 ++ .../Query/Grammars/SqlServerGrammar.php | 15 +++ .../Database/Query/JoinLateralClause.php | 8 ++ tests/Database/DatabaseQueryBuilderTest.php | 111 +++++++++++++++++ .../Database/MySql/JoinLateralTest.php | 117 ++++++++++++++++++ .../Database/Postgres/JoinLateralTest.php | 104 ++++++++++++++++ .../Database/SqlServer/JoinLateralTest.php | 100 +++++++++++++++ 10 files changed, 546 insertions(+) create mode 100644 src/Illuminate/Database/Query/JoinLateralClause.php create mode 100644 tests/Integration/Database/MySql/JoinLateralTest.php create mode 100644 tests/Integration/Database/Postgres/JoinLateralTest.php create mode 100644 tests/Integration/Database/SqlServer/JoinLateralTest.php diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index ebb833707b11..6e31dbb0d263 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -586,6 +586,39 @@ public function joinSub($query, $as, $first, $operator = null, $second = null, $ return $this->join(new Expression($expression), $first, $operator, $second, $type, $where); } + /** + * Add a lateral join clause to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param string $as + * @param string $type + * @return $this + */ + public function joinLateral($query, string $as, string $type = 'inner') + { + [$query, $bindings] = $this->createSub($query); + + $expression = '('.$query.') as '.$this->grammar->wrapTable($as); + + $this->addBinding($bindings, 'join'); + + $this->joins[] = $this->newJoinLateralClause($this, $type, new Expression($expression)); + + return $this; + } + + /** + * Add a lateral left join to the query. + * + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query + * @param string $as + * @return $this + */ + public function leftJoinLateral($query, string $as) + { + return $this->joinLateral($query, $as, 'left'); + } + /** * Add a left join to the query. * @@ -725,6 +758,19 @@ protected function newJoinClause(self $parentQuery, $type, $table) return new JoinClause($parentQuery, $type, $table); } + /** + * Get a new join lateral clause. + * + * @param \Illuminate\Database\Query\Builder $parentQuery + * @param string $type + * @param string $table + * @return \Illuminate\Database\Query\JoinLateralClause + */ + protected function newJoinLateralClause(self $parentQuery, $type, $table) + { + return new JoinLateralClause($parentQuery, $type, $table); + } + /** * Merge an array of where clauses and bindings. * diff --git a/src/Illuminate/Database/Query/Grammars/Grammar.php b/src/Illuminate/Database/Query/Grammars/Grammar.php index a03cdcb03346..b8eed21e69fd 100755 --- a/src/Illuminate/Database/Query/Grammars/Grammar.php +++ b/src/Illuminate/Database/Query/Grammars/Grammar.php @@ -7,6 +7,7 @@ use Illuminate\Database\Grammar as BaseGrammar; use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\JoinClause; +use Illuminate\Database\Query\JoinLateralClause; use Illuminate\Support\Arr; use RuntimeException; @@ -182,10 +183,28 @@ protected function compileJoins(Builder $query, $joins) $tableAndNestedJoins = is_null($join->joins) ? $table : '('.$table.$nestedJoins.')'; + if ($join instanceof JoinLateralClause) { + return $this->compileJoinLateral($join, $tableAndNestedJoins); + } + return trim("{$join->type} join {$tableAndNestedJoins} {$this->compileWheres($join)}"); })->implode(' '); } + /** + * Compile a "lateral join" clause. + * + * @param \Illuminate\Database\Query\JoinLateralClause $join + * @param string $expression + * @return string + * + * @throws \RuntimeException + */ + public function compileJoinLateral(JoinLateralClause $join, string $expression): string + { + throw new RuntimeException('This database engine does not support lateral joins.'); + } + /** * Compile the "where" portions of the query. * diff --git a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php index 1365d8efe620..3d900eeb3c24 100755 --- a/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/MySqlGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Query\Grammars; use Illuminate\Database\Query\Builder; +use Illuminate\Database\Query\JoinLateralClause; use Illuminate\Support\Str; class MySqlGrammar extends Grammar @@ -267,6 +268,18 @@ public function compileUpsert(Builder $query, array $values, array $uniqueBy, ar return $sql.$columns; } + /** + * Compile a "lateral join" clause. + * + * @param \Illuminate\Database\Query\JoinLateralClause $join + * @param string $expression + * @return string + */ + public function compileJoinLateral(JoinLateralClause $join, string $expression): string + { + return trim("{$join->type} join lateral {$expression} on true"); + } + /** * Prepare a JSON column being updated using the JSON_SET function. * diff --git a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php index b39a20a0a5d0..c22720a05c7c 100755 --- a/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/PostgresGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Query\Grammars; use Illuminate\Database\Query\Builder; +use Illuminate\Database\Query\JoinLateralClause; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -409,6 +410,18 @@ public function compileUpsert(Builder $query, array $values, array $uniqueBy, ar return $sql.$columns; } + /** + * Compile a "lateral join" clause. + * + * @param \Illuminate\Database\Query\JoinLateralClause $join + * @param string $expression + * @return string + */ + public function compileJoinLateral(JoinLateralClause $join, string $expression): string + { + return trim("{$join->type} join lateral {$expression} on true"); + } + /** * Prepares a JSON column being updated using the JSONB_SET function. * diff --git a/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php index f68722a64bce..062041c37d30 100755 --- a/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SqlServerGrammar.php @@ -3,6 +3,7 @@ namespace Illuminate\Database\Query\Grammars; use Illuminate\Database\Query\Builder; +use Illuminate\Database\Query\JoinLateralClause; use Illuminate\Support\Arr; use Illuminate\Support\Str; @@ -444,6 +445,20 @@ public function prepareBindingsForUpdate(array $bindings, array $values) ); } + /** + * Compile a "lateral join" clause. + * + * @param \Illuminate\Database\Query\JoinLateralClause $join + * @param string $expression + * @return string + */ + public function compileJoinLateral(JoinLateralClause $join, string $expression): string + { + $type = $join->type == 'left' ? 'outer' : 'cross'; + + return trim("{$type} apply {$expression}"); + } + /** * Compile the SQL statement to define a savepoint. * diff --git a/src/Illuminate/Database/Query/JoinLateralClause.php b/src/Illuminate/Database/Query/JoinLateralClause.php new file mode 100644 index 000000000000..1be31d29626a --- /dev/null +++ b/src/Illuminate/Database/Query/JoinLateralClause.php @@ -0,0 +1,8 @@ +from('users')->rightJoinSub(['foo'], 'sub', 'users.id', '=', 'sub.id'); } + public function testJoinLateral() + { + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->from('users')->joinLateral('select * from `contacts` where `contracts`.`user_id` = `users`.`id`', 'sub'); + $this->assertSame('select * from `users` inner join lateral (select * from `contacts` where `contracts`.`user_id` = `users`.`id`) as `sub` on true', $builder->toSql()); + + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->from('users')->joinLateral(function ($q) { + $q->from('contacts')->whereColumn('contracts.user_id', 'users.id'); + }, 'sub'); + $this->assertSame('select * from `users` inner join lateral (select * from `contacts` where `contracts`.`user_id` = `users`.`id`) as `sub` on true', $builder->toSql()); + + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $sub = $this->getMySqlBuilder(); + $sub->getConnection()->shouldReceive('getDatabaseName'); + $eloquentBuilder = new EloquentBuilder($sub->from('contacts')->whereColumn('contracts.user_id', 'users.id')); + $builder->from('users')->joinLateral($eloquentBuilder, 'sub'); + $this->assertSame('select * from `users` inner join lateral (select * from `contacts` where `contracts`.`user_id` = `users`.`id`) as `sub` on true', $builder->toSql()); + + $sub1 = $this->getMySqlBuilder(); + $sub1->getConnection()->shouldReceive('getDatabaseName'); + $sub1 = $sub1->from('contacts')->whereColumn('contracts.user_id', 'users.id')->where('name', 'foo'); + + $sub2 = $this->getMySqlBuilder(); + $sub2->getConnection()->shouldReceive('getDatabaseName'); + $sub2 = $sub2->from('contacts')->whereColumn('contracts.user_id', 'users.id')->where('name', 'bar'); + + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->from('users')->joinLateral($sub1, 'sub1')->joinLateral($sub2, 'sub2'); + + $expected = 'select * from `users` '; + $expected .= 'inner join lateral (select * from `contacts` where `contracts`.`user_id` = `users`.`id` and `name` = ?) as `sub1` on true '; + $expected .= 'inner join lateral (select * from `contacts` where `contracts`.`user_id` = `users`.`id` and `name` = ?) as `sub2` on true'; + + $this->assertEquals($expected, $builder->toSql()); + $this->assertEquals(['foo', 'bar'], $builder->getRawBindings()['join']); + + $this->expectException(InvalidArgumentException::class); + $builder = $this->getMySqlBuilder(); + $builder->from('users')->joinLateral(['foo'], 'sub'); + } + + public function testJoinLateralSQLite() + { + $this->expectException(RuntimeException::class); + $builder = $this->getSQLiteBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->from('users')->joinLateral(function ($q) { + $q->from('contacts')->whereColumn('contracts.user_id', 'users.id'); + }, 'sub')->toSql(); + } + + public function testJoinLateralPostgres() + { + $builder = $this->getPostgresBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->from('users')->joinLateral(function ($q) { + $q->from('contacts')->whereColumn('contracts.user_id', 'users.id'); + }, 'sub'); + $this->assertSame('select * from "users" inner join lateral (select * from "contacts" where "contracts"."user_id" = "users"."id") as "sub" on true', $builder->toSql()); + } + + public function testJoinLateralSqlServer() + { + $builder = $this->getSqlServerBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->from('users')->joinLateral(function ($q) { + $q->from('contacts')->whereColumn('contracts.user_id', 'users.id'); + }, 'sub'); + $this->assertSame('select * from [users] cross apply (select * from [contacts] where [contracts].[user_id] = [users].[id]) as [sub]', $builder->toSql()); + } + + public function testJoinLateralWithPrefix() + { + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->getGrammar()->setTablePrefix('prefix_'); + $builder->from('users')->joinLateral('select * from `contacts` where `contracts`.`user_id` = `users`.`id`', 'sub'); + $this->assertSame('select * from `prefix_users` inner join lateral (select * from `contacts` where `contracts`.`user_id` = `users`.`id`) as `prefix_sub` on true', $builder->toSql()); + } + + public function testLeftJoinLateral() + { + $builder = $this->getMySqlBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + + $sub = $this->getMySqlBuilder(); + $sub->getConnection()->shouldReceive('getDatabaseName'); + + $builder->from('users')->leftJoinLateral($sub->from('contacts')->whereColumn('contracts.user_id', 'users.id'), 'sub'); + $this->assertSame('select * from `users` left join lateral (select * from `contacts` where `contracts`.`user_id` = `users`.`id`) as `sub` on true', $builder->toSql()); + + $this->expectException(InvalidArgumentException::class); + $builder = $this->getBuilder(); + $builder->from('users')->leftJoinLateral(['foo'], 'sub'); + } + + public function testLeftJoinLateralSqlServer() + { + $builder = $this->getSqlServerBuilder(); + $builder->getConnection()->shouldReceive('getDatabaseName'); + $builder->from('users')->leftJoinLateral(function ($q) { + $q->from('contacts')->whereColumn('contracts.user_id', 'users.id'); + }, 'sub'); + $this->assertSame('select * from [users] outer apply (select * from [contacts] where [contracts].[user_id] = [users].[id]) as [sub]', $builder->toSql()); + } + public function testRawExpressionsInSelect() { $builder = $this->getBuilder(); diff --git a/tests/Integration/Database/MySql/JoinLateralTest.php b/tests/Integration/Database/MySql/JoinLateralTest.php new file mode 100644 index 000000000000..969308ff856c --- /dev/null +++ b/tests/Integration/Database/MySql/JoinLateralTest.php @@ -0,0 +1,117 @@ +id('id'); + $table->string('name'); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->id('id'); + $table->string('title'); + $table->integer('rating'); + $table->unsignedBigInteger('user_id'); + }); + } + + protected function destroyDatabaseMigrations() + { + Schema::drop('posts'); + Schema::drop('users'); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->checkMySqlVersion(); + + DB::table('users')->insert([ + ['name' => Str::random()], + ['name' => Str::random()], + ]); + + DB::table('posts')->insert([ + ['title' => Str::random(), 'rating' => 1, 'user_id' => 1], + ['title' => Str::random(), 'rating' => 3, 'user_id' => 1], + ['title' => Str::random(), 'rating' => 7, 'user_id' => 1], + ]); + } + + protected function checkMySqlVersion() + { + $mySqlVersion = DB::select('select version()')[0]->{'version()'} ?? ''; + + if (strpos($mySqlVersion, 'Maria') !== false) { + $this->markTestSkipped('Lateral joins are not supported on MariaDB'.__CLASS__); + } elseif ((float) $mySqlVersion < '8.0.14') { + $this->markTestSkipped('Lateral joins are not supported on MySQL < 8.0.14'.__CLASS__); + } + } + + public function testJoinLateral() + { + $subquery = DB::table('posts') + ->select('title as best_post_title', 'rating as best_post_rating') + ->whereColumn('user_id', 'users.id') + ->orderBy('rating', 'desc') + ->limit(2); + + $userWithPosts = DB::table('users') + ->where('id', 1) + ->joinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(2, $userWithPosts); + $this->assertEquals(7, $userWithPosts[0]->best_post_rating); + $this->assertEquals(3, $userWithPosts[1]->best_post_rating); + + $userWithoutPosts = DB::table('users') + ->where('id', 2) + ->joinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(0, $userWithoutPosts); + } + + public function testLeftJoinLateral() + { + $subquery = DB::table('posts') + ->select('title as best_post_title', 'rating as best_post_rating') + ->whereColumn('user_id', 'users.id') + ->orderBy('rating', 'desc') + ->limit(2); + + $userWithPosts = DB::table('users') + ->where('id', 1) + ->leftJoinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(2, $userWithPosts); + $this->assertEquals(7, $userWithPosts[0]->best_post_rating); + $this->assertEquals(3, $userWithPosts[1]->best_post_rating); + + $userWithoutPosts = DB::table('users') + ->where('id', 2) + ->leftJoinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(1, $userWithoutPosts); + $this->assertNull($userWithoutPosts[0]->best_post_title); + $this->assertNull($userWithoutPosts[0]->best_post_rating); + } +} diff --git a/tests/Integration/Database/Postgres/JoinLateralTest.php b/tests/Integration/Database/Postgres/JoinLateralTest.php new file mode 100644 index 000000000000..e17f5622efdd --- /dev/null +++ b/tests/Integration/Database/Postgres/JoinLateralTest.php @@ -0,0 +1,104 @@ +id('id'); + $table->string('name'); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->id('id'); + $table->string('title'); + $table->integer('rating'); + $table->unsignedBigInteger('user_id'); + }); + } + + protected function destroyDatabaseMigrations() + { + Schema::drop('posts'); + Schema::drop('users'); + } + + protected function setUp(): void + { + parent::setUp(); + + DB::table('users')->insert([ + ['name' => Str::random()], + ['name' => Str::random()], + ]); + + DB::table('posts')->insert([ + ['title' => Str::random(), 'rating' => 1, 'user_id' => 1], + ['title' => Str::random(), 'rating' => 3, 'user_id' => 1], + ['title' => Str::random(), 'rating' => 7, 'user_id' => 1], + ]); + } + + public function testJoinLateral() + { + $subquery = DB::table('posts') + ->select('title as best_post_title', 'rating as best_post_rating') + ->whereColumn('user_id', 'users.id') + ->orderBy('rating', 'desc') + ->limit(2); + + $userWithPosts = DB::table('users') + ->where('id', 1) + ->joinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(2, $userWithPosts); + $this->assertEquals(7, $userWithPosts[0]->best_post_rating); + $this->assertEquals(3, $userWithPosts[1]->best_post_rating); + + $userWithoutPosts = DB::table('users') + ->where('id', 2) + ->joinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(0, $userWithoutPosts); + } + + public function testLeftJoinLateral() + { + $subquery = DB::table('posts') + ->select('title as best_post_title', 'rating as best_post_rating') + ->whereColumn('user_id', 'users.id') + ->orderBy('rating', 'desc') + ->limit(2); + + $userWithPosts = DB::table('users') + ->where('id', 1) + ->leftJoinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(2, $userWithPosts); + $this->assertEquals(7, $userWithPosts[0]->best_post_rating); + $this->assertEquals(3, $userWithPosts[1]->best_post_rating); + + $userWithoutPosts = DB::table('users') + ->where('id', 2) + ->leftJoinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(1, $userWithoutPosts); + $this->assertNull($userWithoutPosts[0]->best_post_title); + $this->assertNull($userWithoutPosts[0]->best_post_rating); + } +} diff --git a/tests/Integration/Database/SqlServer/JoinLateralTest.php b/tests/Integration/Database/SqlServer/JoinLateralTest.php new file mode 100644 index 000000000000..df11c5517585 --- /dev/null +++ b/tests/Integration/Database/SqlServer/JoinLateralTest.php @@ -0,0 +1,100 @@ +id('id'); + $table->string('name'); + }); + + Schema::create('posts', function (Blueprint $table) { + $table->id('id'); + $table->string('title'); + $table->integer('rating'); + $table->unsignedBigInteger('user_id'); + }); + } + + protected function destroyDatabaseMigrations() + { + Schema::drop('posts'); + Schema::drop('users'); + } + + protected function setUp(): void + { + parent::setUp(); + + DB::table('users')->insert([ + ['name' => Str::random()], + ['name' => Str::random()], + ]); + + DB::table('posts')->insert([ + ['title' => Str::random(), 'rating' => 1, 'user_id' => 1], + ['title' => Str::random(), 'rating' => 3, 'user_id' => 1], + ['title' => Str::random(), 'rating' => 7, 'user_id' => 1], + ]); + } + + public function testJoinLateral() + { + $subquery = DB::table('posts') + ->select('title as best_post_title', 'rating as best_post_rating') + ->whereColumn('user_id', 'users.id') + ->orderBy('rating', 'desc') + ->limit(2); + + $userWithPosts = DB::table('users') + ->where('id', 1) + ->joinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(2, $userWithPosts); + $this->assertEquals(7, (int) $userWithPosts[0]->best_post_rating); + $this->assertEquals(3, (int) $userWithPosts[1]->best_post_rating); + + $userWithoutPosts = DB::table('users') + ->where('id', 2) + ->joinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(0, $userWithoutPosts); + } + + public function testLeftJoinLateral() + { + $subquery = DB::table('posts') + ->select('title as best_post_title', 'rating as best_post_rating') + ->whereColumn('user_id', 'users.id') + ->orderBy('rating', 'desc') + ->limit(2); + + $userWithPosts = DB::table('users') + ->where('id', 1) + ->leftJoinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(2, $userWithPosts); + $this->assertEquals(7, (int) $userWithPosts[0]->best_post_rating); + $this->assertEquals(3, (int) $userWithPosts[1]->best_post_rating); + + $userWithoutPosts = DB::table('users') + ->where('id', 2) + ->leftJoinLateral($subquery, 'best_post') + ->get(); + + $this->assertCount(1, $userWithoutPosts); + $this->assertNull($userWithoutPosts[0]->best_post_title); + $this->assertNull($userWithoutPosts[0]->best_post_rating); + } +} From 778683570e1a9d0a8a0da86f43f710edbd928a51 Mon Sep 17 00:00:00 2001 From: Amir Reza Mehrbakhsh Date: Mon, 26 Feb 2024 09:10:21 +0100 Subject: [PATCH 139/325] Update return type (#50252) --- src/Illuminate/Auth/TokenGuard.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Auth/TokenGuard.php b/src/Illuminate/Auth/TokenGuard.php index b1aa7a7e5162..7fe5a9f7802a 100644 --- a/src/Illuminate/Auth/TokenGuard.php +++ b/src/Illuminate/Auth/TokenGuard.php @@ -92,7 +92,7 @@ public function user() /** * Get the token for the current request. * - * @return string + * @return string|null */ public function getTokenForRequest() { From 6bc9f5a7c86e1caf4782a2ea672fa7e018bb84b7 Mon Sep 17 00:00:00 2001 From: Michael Nabil <46572405+michaelnabil230@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:40:48 +0200 Subject: [PATCH 140/325] Fix dockblock (#50259) --- src/Illuminate/Cache/Console/PruneStaleTagsCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Cache/Console/PruneStaleTagsCommand.php b/src/Illuminate/Cache/Console/PruneStaleTagsCommand.php index 95665f9bab87..dbb2f6bd0860 100644 --- a/src/Illuminate/Cache/Console/PruneStaleTagsCommand.php +++ b/src/Illuminate/Cache/Console/PruneStaleTagsCommand.php @@ -29,7 +29,7 @@ class PruneStaleTagsCommand extends Command * Execute the console command. * * @param \Illuminate\Cache\CacheManager $cache - * @return void + * @return int|null */ public function handle(CacheManager $cache) { From 9fde8147a38aea882922e5fe1a33604191ff43b5 Mon Sep 17 00:00:00 2001 From: Michael Nabil <46572405+michaelnabil230@users.noreply.github.com> Date: Mon, 26 Feb 2024 18:01:02 +0200 Subject: [PATCH 141/325] Add `Conditionable` trait (#50257) --- src/Illuminate/Validation/Rules/Enum.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Illuminate/Validation/Rules/Enum.php b/src/Illuminate/Validation/Rules/Enum.php index 42d9e6f012d2..62114626eca7 100644 --- a/src/Illuminate/Validation/Rules/Enum.php +++ b/src/Illuminate/Validation/Rules/Enum.php @@ -5,10 +5,13 @@ use Illuminate\Contracts\Validation\Rule; use Illuminate\Contracts\Validation\ValidatorAwareRule; use Illuminate\Support\Arr; +use Illuminate\Support\Traits\Conditionable; use TypeError; class Enum implements Rule, ValidatorAwareRule { + use Conditionable; + /** * The type of the enum. * From b8b71e0b9cec4446f5f057e53b9cd900bf8994cd Mon Sep 17 00:00:00 2001 From: Fabien Villepinte Date: Mon, 26 Feb 2024 17:06:02 +0100 Subject: [PATCH 142/325] [10.x] Update Facade::$app to nullable (#50260) --- src/Illuminate/Support/Facades/Facade.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Facades/Facade.php b/src/Illuminate/Support/Facades/Facade.php index 1dbdc321543b..2dbf100cd5d1 100755 --- a/src/Illuminate/Support/Facades/Facade.php +++ b/src/Illuminate/Support/Facades/Facade.php @@ -18,7 +18,7 @@ abstract class Facade /** * The application instance being facaded. * - * @var \Illuminate\Contracts\Foundation\Application + * @var \Illuminate\Contracts\Foundation\Application|null */ protected static $app; @@ -317,7 +317,7 @@ public static function defaultAliases() /** * Get the application instance behind the facade. * - * @return \Illuminate\Contracts\Foundation\Application + * @return \Illuminate\Contracts\Foundation\Application|null */ public static function getFacadeApplication() { @@ -327,7 +327,7 @@ public static function getFacadeApplication() /** * Set the application instance. * - * @param \Illuminate\Contracts\Foundation\Application $app + * @param \Illuminate\Contracts\Foundation\Application|null $app * @return void */ public static function setFacadeApplication($app) From fa1be10279603a260ea97dbcdf42e33e4c7c6ef6 Mon Sep 17 00:00:00 2001 From: Kit Loong Date: Tue, 27 Feb 2024 00:14:36 +0800 Subject: [PATCH 143/325] Truncate sqlite table with prefix (#50251) --- .../Database/Query/Grammars/SQLiteGrammar.php | 2 +- tests/Database/DatabaseQueryBuilderTest.php | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php index 9a61a69c7108..e4794234686d 100755 --- a/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Query/Grammars/SQLiteGrammar.php @@ -387,7 +387,7 @@ protected function compileDeleteWithJoinsOrLimit(Builder $query) public function compileTruncate(Builder $query) { return [ - 'delete from sqlite_sequence where name = ?' => [$query->from], + 'delete from sqlite_sequence where name = ?' => [$this->getTablePrefix().$query->from], 'delete from '.$this->wrapTable($query->from) => [], ]; } diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 9f63a3e1f94a..ab626dfad9c1 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -3592,6 +3592,23 @@ public function testTruncateMethod() ], $sqlite->compileTruncate($builder)); } + public function testTruncateMethodWithPrefix() + { + $builder = $this->getBuilder(); + $builder->getGrammar()->setTablePrefix('prefix_'); + $builder->getConnection()->shouldReceive('statement')->once()->with('truncate table "prefix_users"', []); + $builder->from('users')->truncate(); + + $sqlite = new SQLiteGrammar; + $sqlite->setTablePrefix('prefix_'); + $builder = $this->getBuilder(); + $builder->from('users'); + $this->assertEquals([ + 'delete from sqlite_sequence where name = ?' => ['prefix_users'], + 'delete from "prefix_users"' => [], + ], $sqlite->compileTruncate($builder)); + } + public function testPreserveAddsClosureToArray() { $builder = $this->getBuilder(); From 988206bcacf79829bda66af4a016778e4b4d762a Mon Sep 17 00:00:00 2001 From: wq9578 <121522862+wq9578@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:20:06 +0100 Subject: [PATCH 144/325] =?UTF-8?q?Correction=20comment=20for=20Str::order?= =?UTF-8?q?edUuid()=20-=20https://github.com/larave=E2=80=A6=20(#50268)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Correction comment for Str::orderedUuid() - https://github.com/laravel/framework/issues/50231 * Update Str.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Support/Str.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index c866d7d2b39c..1a27bd1f48f1 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1634,7 +1634,7 @@ public static function uuid() } /** - * Generate a time-ordered UUID (version 4). + * Generate a time-ordered UUID. * * @return \Ramsey\Uuid\UuidInterface */ From 5e95946a8283a8d5c015035793f9c61c297e937f Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 27 Feb 2024 16:46:54 +0000 Subject: [PATCH 145/325] Update version to v10.46.0 --- 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 ea5cbe2ca532..325bd1ccd6a4 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.45.1'; + const VERSION = '10.46.0'; /** * The base path for the Laravel installation. From eb66928b4aaa3fa84bdd95db44d27fc7e0977563 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 27 Feb 2024 16:48:48 +0000 Subject: [PATCH 146/325] Update CHANGELOG --- CHANGELOG.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ba419c3be85..4866acf56e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.45.1...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.46.0...10.x) + +## [v10.46.0](https://github.com/laravel/framework/compare/v10.45.1...v10.46.0) - 2024-02-27 + +* [10.x] Ensure lazy-loading for trashed morphTo relations works by [@nuernbergerA](https://github.com/nuernbergerA) in https://github.com/laravel/framework/pull/50176 +* [10.x] Arr::select not working when $keys is a string by [@Sicklou](https://github.com/Sicklou) in https://github.com/laravel/framework/pull/50169 +* [10.x] Added passing loaded relationship to value callback by [@dkulyk](https://github.com/dkulyk) in https://github.com/laravel/framework/pull/50167 +* [10.x] Fix optional charset and collation when creating database by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/50168 +* [10.x] update doc block in PendingProcess.php by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50198 +* [10.x] Fix Accepting nullable Parameters, updated doc block, and null pointer exception handling in batchable trait by [@saMahmoudzadeh](https://github.com/saMahmoudzadeh) in https://github.com/laravel/framework/pull/50209 +* Make GuardsAttributes fillable property DocBlock more specific by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/50229 +* [10.x] Add only and except methods to Enum validation rule by [@Anton5360](https://github.com/Anton5360) in https://github.com/laravel/framework/pull/50226 +* [10.x] Fixes on nesting operations performed while applying scopes. by [@Guilhem-DELAITRE](https://github.com/Guilhem-DELAITRE) in https://github.com/laravel/framework/pull/50207 +* [10.x] Custom RateLimiter increase by [@khepin](https://github.com/khepin) in https://github.com/laravel/framework/pull/50197 +* [10.x] Add Lateral Join to Query Builder by [@Bakke](https://github.com/Bakke) in https://github.com/laravel/framework/pull/50050 +* [10.x] Update return type by [@AmirRezaM75](https://github.com/AmirRezaM75) in https://github.com/laravel/framework/pull/50252 +* [10.x] Fix dockblock by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/50259 +* [10.x] Add `Conditionable` in enum rule by [@michaelnabil230](https://github.com/michaelnabil230) in https://github.com/laravel/framework/pull/50257 +* [10.x] Update Facade::$app to nullable by [@villfa](https://github.com/villfa) in https://github.com/laravel/framework/pull/50260 +* [10.x] Truncate sqlite table name with prefix by [@kitloong](https://github.com/kitloong) in https://github.com/laravel/framework/pull/50251 +* Correction comment for Str::orderedUuid() - https://github.com/larave… by [@wq9578](https://github.com/wq9578) in https://github.com/laravel/framework/pull/50268 ## [v10.45.1](https://github.com/laravel/framework/compare/v10.45.0...v10.45.1) - 2024-02-21 From 698c74b8465daa518cf3954eea133e4e6e3f47d7 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 28 Feb 2024 22:46:44 +0800 Subject: [PATCH 147/325] [10.x] Update GitHub Actions dependencies (#50293) Signed-off-by: Mior Muhammad Zaki --- .github/workflows/databases.yml | 10 +++++----- .github/workflows/facades.yml | 2 +- .github/workflows/queues.yml | 8 ++++---- .github/workflows/static-analysis.yml | 2 +- .github/workflows/tests.yml | 12 ++++++------ 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index 2bb38f0f1ab2..41bb82da9666 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -41,7 +41,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -86,7 +86,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -131,7 +131,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -177,7 +177,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -221,7 +221,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 diff --git a/.github/workflows/facades.yml b/.github/workflows/facades.yml index fe4b95f2f4b3..af8eb9b60223 100644 --- a/.github/workflows/facades.yml +++ b/.github/workflows/facades.yml @@ -31,7 +31,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml index d8f37ab3cac7..ef08a648a1fe 100644 --- a/.github/workflows/queues.yml +++ b/.github/workflows/queues.yml @@ -31,7 +31,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -65,7 +65,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -107,7 +107,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -145,7 +145,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index d7f42b4641d8..76a0be9b42e5 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -32,7 +32,7 @@ jobs: coverage: none - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c186ecd54e48..53f2553015d2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,14 +63,14 @@ jobs: REDIS_LIBS: liblz4-dev, liblzf-dev, libzstd-dev - name: Set minimum PHP 8.1 versions - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 command: composer require symfony/css-selector:^6.0 --no-interaction --no-update - name: Set minimum PHP 8.2 versions - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -78,7 +78,7 @@ jobs: if: matrix.php >= 8.2 - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -133,14 +133,14 @@ jobs: coverage: none - name: Set Minimum PHP 8.1 Versions - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 command: composer require symfony/css-selector:~6.0 --no-interaction --no-update - name: Set Minimum PHP 8.2 Versions - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 @@ -148,7 +148,7 @@ jobs: if: matrix.php >= 8.2 - name: Install dependencies - uses: nick-fields/retry@v2 + uses: nick-fields/retry@v3 with: timeout_minutes: 5 max_attempts: 5 From 3646b5112b2d4e1a86d4f5403f6e9317b535d061 Mon Sep 17 00:00:00 2001 From: Anders Jenbo Date: Thu, 29 Feb 2024 18:35:00 +0100 Subject: [PATCH 148/325] Allow for relation key to be an enum (#50311) * Allow for relation key to be an enum If a foring key is being cast to an enum calling ->sync() will cause a TypeError: Illegal offset type. This resolves the issue by getting the value before using it as an array key. * Update InteractsWithPivotTable.php --------- Co-authored-by: Taylor Otwell --- .../Eloquent/Relations/Concerns/InteractsWithPivotTable.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php index 04862e5d80f7..48444a52db85 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/InteractsWithPivotTable.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\Eloquent\Relations\Concerns; +use BackedEnum; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\Pivot; @@ -154,6 +155,10 @@ protected function formatRecordsList(array $records) [$id, $attributes] = [$attributes, []]; } + if ($id instanceof BackedEnum) { + $id = $id->value; + } + return [$id => $attributes]; })->all(); } From 5a1fa47247ba7a1c237dcee4ec53fcaa2c5c78fa Mon Sep 17 00:00:00 2001 From: Tiago Fernandes Date: Sat, 2 Mar 2024 00:22:09 +0000 Subject: [PATCH 149/325] FIx for "empty" strings (#50335) Avoid exception passed string is just spaces. Example: str(' ')->apa() was failing. --- src/Illuminate/Support/Str.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 1a27bd1f48f1..ed1f1b5d6834 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1298,7 +1298,7 @@ public static function headline($value) */ public static function apa($value) { - if ($value === '') { + if (trim($value) === '') { return $value; } From 0673cb72cb164b2b0de2064bc34fef69310e3c77 Mon Sep 17 00:00:00 2001 From: Derek Myers Date: Fri, 1 Mar 2024 18:23:55 -0600 Subject: [PATCH 150/325] Fixed header mail text component to not use markdown (#50332) --- src/Illuminate/Mail/resources/views/text/header.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Mail/resources/views/text/header.blade.php b/src/Illuminate/Mail/resources/views/text/header.blade.php index aaa3e5754446..97444ebdcfd1 100644 --- a/src/Illuminate/Mail/resources/views/text/header.blade.php +++ b/src/Illuminate/Mail/resources/views/text/header.blade.php @@ -1 +1 @@ -[{{ $slot }}]({{ $url }}) +{{ $slot }}: {{ $url }} From 119a3a3257090edc2919d41ded300cbc3c36938a Mon Sep 17 00:00:00 2001 From: Ostap Brehin Date: Sat, 2 Mar 2024 16:35:54 +0000 Subject: [PATCH 151/325] Add test for the "empty strings in `Str::apa()`" fix (#50340) --- tests/Support/SupportStrTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 552d9efd2dbd..e61ae5e4e249 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -98,6 +98,7 @@ public function testStringApa() $this->assertSame('To Kill a Mockingbird', Str::apa('To Kill A Mockingbird')); $this->assertSame('', Str::apa('')); + $this->assertSame(' ', Str::apa(' ')); } public function testStringWithoutWordsDoesntProduceError() From 928d30f5dd092174944cf58abf5b7c56f34d6d56 Mon Sep 17 00:00:00 2001 From: Kay W Date: Mon, 4 Mar 2024 22:05:29 +0800 Subject: [PATCH 152/325] Fix the cache cannot expire cache with `0` TTL (#50359) --- src/Illuminate/Cache/RedisTaggedCache.php | 24 +++++++++------------- tests/Integration/Cache/RedisStoreTest.php | 17 +++++++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/Illuminate/Cache/RedisTaggedCache.php b/src/Illuminate/Cache/RedisTaggedCache.php index 75c1001ce747..8846844b413d 100644 --- a/src/Illuminate/Cache/RedisTaggedCache.php +++ b/src/Illuminate/Cache/RedisTaggedCache.php @@ -19,16 +19,14 @@ public function add($key, $value, $ttl = null) if ($ttl !== null) { $seconds = $this->getSeconds($ttl); - if ($seconds <= 0) { - return false; + if ($seconds > 0) { + $this->tags->addEntry( + $this->itemKey($key), + $seconds + ); } } - $this->tags->addEntry( - $this->itemKey($key), - $seconds - ); - return parent::add($key, $value, $ttl); } @@ -48,15 +46,13 @@ public function put($key, $value, $ttl = null) $seconds = $this->getSeconds($ttl); - if ($seconds <= 0) { - return false; + if ($seconds > 0) { + $this->tags->addEntry( + $this->itemKey($key), + $seconds + ); } - $this->tags->addEntry( - $this->itemKey($key), - $seconds - ); - return parent::put($key, $value, $ttl); } diff --git a/tests/Integration/Cache/RedisStoreTest.php b/tests/Integration/Cache/RedisStoreTest.php index dd0a1943b246..821e33ded66d 100644 --- a/tests/Integration/Cache/RedisStoreTest.php +++ b/tests/Integration/Cache/RedisStoreTest.php @@ -76,6 +76,20 @@ public function testItCanStoreNan() $this->assertNan(Cache::store('redis')->get('foo')); } + public function testItCanExpireWithZeroTTL() + { + Cache::store('redis')->clear(); + + $result = Cache::store('redis')->put('foo', 10,10); + $this->assertTrue($result); + + $result = Cache::store('redis')->put('foo', 10,0); + $this->assertTrue($result); + + $value = Cache::store('redis')->get('foo'); + $this->assertNull($value); + } + public function testTagsCanBeAccessed() { Cache::store('redis')->clear(); @@ -146,6 +160,9 @@ public function testPastTtlTagEntriesAreNotAdded() Cache::store('redis')->tags(['votes'])->add('person-1', 0, new DateTime('yesterday')); + $value = Cache::store('redis')->tags(['votes'])->get('person-1'); + $this->assertNull($value); + $keyCount = Cache::store('redis')->connection()->keys('*'); $this->assertEquals(0, count($keyCount)); } From 9b97007a0158c6c4ec4e88bc412591cfc408bbe1 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 4 Mar 2024 14:05:58 +0000 Subject: [PATCH 153/325] Apply fixes from StyleCI --- tests/Integration/Cache/RedisStoreTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/Cache/RedisStoreTest.php b/tests/Integration/Cache/RedisStoreTest.php index 821e33ded66d..1d748cf24525 100644 --- a/tests/Integration/Cache/RedisStoreTest.php +++ b/tests/Integration/Cache/RedisStoreTest.php @@ -80,10 +80,10 @@ public function testItCanExpireWithZeroTTL() { Cache::store('redis')->clear(); - $result = Cache::store('redis')->put('foo', 10,10); + $result = Cache::store('redis')->put('foo', 10, 10); $this->assertTrue($result); - $result = Cache::store('redis')->put('foo', 10,0); + $result = Cache::store('redis')->put('foo', 10, 0); $this->assertTrue($result); $value = Cache::store('redis')->get('foo'); From d562c0628892fbdd051640fddcaf669fcb08d8ec Mon Sep 17 00:00:00 2001 From: Saeed Hosseini <39903221+saeedhosseiinii@users.noreply.github.com> Date: Mon, 4 Mar 2024 18:11:04 +0330 Subject: [PATCH 154/325] [10.x] Add fail on timeout to queue listener (#50352) * feat: add fail on timeout to queue listener * Update CallQueuedListener.php * Update Dispatcher.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Events/CallQueuedListener.php | 7 +++++++ src/Illuminate/Events/Dispatcher.php | 1 + 2 files changed, 8 insertions(+) diff --git a/src/Illuminate/Events/CallQueuedListener.php b/src/Illuminate/Events/CallQueuedListener.php index 6a39008520c8..4fb66266359a 100644 --- a/src/Illuminate/Events/CallQueuedListener.php +++ b/src/Illuminate/Events/CallQueuedListener.php @@ -68,6 +68,13 @@ class CallQueuedListener implements ShouldQueue */ public $timeout; + /** + * Indicates if the job should fail if the timeout is exceeded. + * + * @var bool + */ + public $failOnTimeout = false; + /** * Indicates if the job should be encrypted. * diff --git a/src/Illuminate/Events/Dispatcher.php b/src/Illuminate/Events/Dispatcher.php index 2f3ac4cb9a76..22993c36f94f 100755 --- a/src/Illuminate/Events/Dispatcher.php +++ b/src/Illuminate/Events/Dispatcher.php @@ -674,6 +674,7 @@ protected function propagateListenerOptions($listener, $job) $job->retryUntil = method_exists($listener, 'retryUntil') ? $listener->retryUntil(...$data) : null; $job->shouldBeEncrypted = $listener instanceof ShouldBeEncrypted; $job->timeout = $listener->timeout ?? null; + $job->failOnTimeout = $listener->failOnTimeout ?? false; $job->tries = $listener->tries ?? null; $job->through(array_merge( From d7235b38c0f9a1fda40e501bc44576aaae149495 Mon Sep 17 00:00:00 2001 From: Tim Withers Date: Mon, 4 Mar 2024 11:11:18 -0700 Subject: [PATCH 155/325] [10.x] Support sort option flags on sortByMany Collections (#50269) * [10.x] Support sort option flags on sortByMany Collections * Added docblock * Remove commented code * Styling fix * Update tests/Support/SupportCollectionTest.php Co-authored-by: Mior Muhammad Zaki * Removed phpunit attribute * Update tests/Support/SupportCollectionTest.php --------- Co-authored-by: Mior Muhammad Zaki Co-authored-by: Dries Vints --- src/Illuminate/Collections/Collection.php | 23 ++++-- tests/Support/SupportCollectionTest.php | 86 ++++++++++++++++++++++- 2 files changed, 103 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 6a2599946e7c..5716847138b8 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1397,7 +1397,7 @@ public function sortDesc($options = SORT_REGULAR) public function sortBy($callback, $options = SORT_REGULAR, $descending = false) { if (is_array($callback) && ! is_callable($callback)) { - return $this->sortByMany($callback); + return $this->sortByMany($callback, $options); } $results = []; @@ -1428,13 +1428,14 @@ public function sortBy($callback, $options = SORT_REGULAR, $descending = false) * Sort the collection using multiple comparisons. * * @param array $comparisons + * @param int $options * @return static */ - protected function sortByMany(array $comparisons = []) + protected function sortByMany(array $comparisons = [], int $options = SORT_REGULAR) { $items = $this->items; - uasort($items, function ($a, $b) use ($comparisons) { + uasort($items, function ($a, $b) use ($comparisons, $options) { foreach ($comparisons as $comparison) { $comparison = Arr::wrap($comparison); @@ -1452,7 +1453,21 @@ protected function sortByMany(array $comparisons = []) $values = array_reverse($values); } - $result = $values[0] <=> $values[1]; + if (($options & SORT_FLAG_CASE) === SORT_FLAG_CASE) { + if (($options & SORT_NATURAL) === SORT_NATURAL) { + $result = strnatcasecmp($values[0], $values[1]); + } else { + $result = strcasecmp($values[0], $values[1]); + } + } else { + $result = match ($options) { + SORT_NUMERIC => intval($values[0]) <=> intval($values[1]), + SORT_STRING => strcmp($values[0], $values[1]), + SORT_NATURAL => strnatcmp($values[0], $values[1]), + SORT_LOCALE_STRING => strcoll($values[0], $values[1]), + default => $values[0] <=> $values[1], + }; + } } if ($result === 0) { diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 15410edf4ddf..eea0bde94c42 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -2031,9 +2031,9 @@ public function testSortByString($collection) $this->assertEquals([['name' => 'dayle'], ['name' => 'taylor']], array_values($data->all())); $data = new $collection([['name' => 'taylor'], ['name' => 'dayle']]); - $data = $data->sortBy('name', SORT_STRING); + $data = $data->sortBy('name', SORT_STRING, true); - $this->assertEquals([['name' => 'dayle'], ['name' => 'taylor']], array_values($data->all())); + $this->assertEquals([['name' => 'taylor'], ['name' => 'dayle']], array_values($data->all())); } /** @@ -2077,6 +2077,88 @@ public function testSortByAlwaysReturnsAssoc($collection) $this->assertEquals([1 => ['sort' => 1], 0 => ['sort' => 2]], $data->all()); } + #[DataProvider('collectionClassProvider')] + public function testSortByMany($collection) + { + $data = new $collection([['item' => '1'], ['item' => '10'], ['item' => 5], ['item' => 20]]); + $expected = $data->pluck('item')->toArray(); + + sort($expected); + $data = $data->sortBy(['item']); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + rsort($expected); + $data = $data->sortBy([['item', 'desc']]); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + sort($expected, SORT_STRING); + $data = $data->sortBy(['item'], SORT_STRING); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + rsort($expected, SORT_STRING); + $data = $data->sortBy([['item', 'desc']], SORT_STRING); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + sort($expected, SORT_NUMERIC); + $data = $data->sortBy(['item'], SORT_NUMERIC); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + rsort($expected, SORT_NUMERIC); + $data = $data->sortBy([['item', 'desc']], SORT_NUMERIC); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + $data = new $collection([['item' => 'img1'], ['item' => 'img101'], ['item' => 'img10'], ['item' => 'img11']]); + $expected = $data->pluck('item')->toArray(); + + sort($expected, SORT_NUMERIC); + $data = $data->sortBy(['item'], SORT_NUMERIC); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + sort($expected); + $data = $data->sortBy(['item']); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + sort($expected, SORT_NATURAL); + $data = $data->sortBy(['item'], SORT_NATURAL); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + $data = new $collection([['item' => 'img1'], ['item' => 'Img101'], ['item' => 'img10'], ['item' => 'Img11']]); + $expected = $data->pluck('item')->toArray(); + + sort($expected); + $data = $data->sortBy(['item']); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + sort($expected, SORT_NATURAL | SORT_FLAG_CASE); + $data = $data->sortBy(['item'], SORT_NATURAL | SORT_FLAG_CASE); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + sort($expected, SORT_FLAG_CASE | SORT_STRING); + $data = $data->sortBy(['item'], SORT_FLAG_CASE | SORT_STRING); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + sort($expected, SORT_FLAG_CASE | SORT_NUMERIC); + $data = $data->sortBy(['item'], SORT_FLAG_CASE | SORT_NUMERIC); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + $data = new $collection([['item' => 'Österreich'], ['item' => 'Oesterreich'], ['item' => 'Zeta']]); + $expected = $data->pluck('item')->toArray(); + + sort($expected); + $data = $data->sortBy(['item']); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + sort($expected, SORT_LOCALE_STRING); + $data = $data->sortBy(['item'], SORT_LOCALE_STRING); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + + setlocale(LC_ALL, 'de_DE'); + + sort($expected, SORT_LOCALE_STRING); + $data = $data->sortBy(['item'], SORT_LOCALE_STRING); + $this->assertEquals($data->pluck('item')->toArray(), $expected); + } + /** * @dataProvider collectionClassProvider */ From 5050195f1841f1f07be4f1a61a2906c32afb67ad Mon Sep 17 00:00:00 2001 From: Alexander <59757250+musiermoore@users.noreply.github.com> Date: Tue, 5 Mar 2024 03:32:24 +0600 Subject: [PATCH 156/325] [10.x] Add `whereAll` and `whereAny` methods to the query builder (#50344) * Add "whereMultiple" method to the Query Builder * Add a test for the "whereMultiple" method * Add a missing argument to 'assertSame' in the test * Add new checks to test the method with different arguments * Fix preparing of columnsBoolean and operator arguments * Convert operators in the test to lowercase * Trigger tests * Fix formatting (StyleCI) * Split 'whereMultiple' method to whereAll and whereAny * Add tests for orWhereAll and orWhereAny methods * Remove flatting value from 'whereMultiple' method * Remove 'whereMultiple' method * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Query/Builder.php | 74 +++++++++++++++++++++ tests/Database/DatabaseQueryBuilderTest.php | 62 +++++++++++++++++ 2 files changed, 136 insertions(+) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 6e31dbb0d263..f5c40f841b97 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -2095,6 +2095,80 @@ public function orWhereFullText($columns, $value, array $options = []) return $this->whereFulltext($columns, $value, $options, 'or'); } + /** + * Add a "where" clause to the query for multiple columns with "and" conditions between them. + * + * @param string[] $columns + * @param mixed $operator + * @param mixed $value + * @param string $boolean + * @return $this + */ + public function whereAll($columns, $operator = null, $value = null, $boolean = 'and') + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + $this->whereNested(function ($query) use ($columns, $operator, $value) { + foreach ($columns as $column) { + $query->where($column, $operator, $value, 'and'); + } + }, $boolean); + + return $this; + } + + /** + * Add an "or where" clause to the query for multiple columns with "and" conditions between them. + * + * @param string[] $columns + * @param string $operator + * @param mixed $value + * @return $this + */ + public function orWhereAll($columns, $operator = null, $value = null) + { + return $this->whereAll($columns, $operator, $value, 'or'); + } + + /** + * Add an "where" clause to the query for multiple columns with "or" conditions between them. + * + * @param string[] $columns + * @param string $operator + * @param mixed $value + * @param string $boolean + * @return $this + */ + public function whereAny($columns, $operator = null, $value = null, $boolean = 'and') + { + [$value, $operator] = $this->prepareValueAndOperator( + $value, $operator, func_num_args() === 2 + ); + + $this->whereNested(function ($query) use ($columns, $operator, $value) { + foreach ($columns as $column) { + $query->where($column, $operator, $value, 'or'); + } + }, $boolean); + + return $this; + } + + /** + * Add an "or where" clause to the query for multiple columns with "or" conditions between them. + * + * @param string[] $columns + * @param string $operator + * @param mixed $value + * @return $this + */ + public function orWhereAny($columns, $operator = null, $value = null) + { + return $this->whereAny($columns, $operator, $value, 'or'); + } + /** * Add a "group by" clause to the query. * diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index ab626dfad9c1..40500d9ed514 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -1185,6 +1185,68 @@ public function testWhereFulltextPostgres() $this->assertEquals(['Car Plane'], $builder->getBindings()); } + public function testWhereAll() + { + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereAll(['last_name', 'email'], '%Otwell%'); + $this->assertSame('select * from "users" where ("last_name" = ? and "email" = ?)', $builder->toSql()); + $this->assertEquals(['%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereAll(['last_name', 'email'], 'not like', '%Otwell%'); + $this->assertSame('select * from "users" where ("last_name" not like ? and "email" not like ?)', $builder->toSql()); + $this->assertEquals(['%Otwell%', '%Otwell%'], $builder->getBindings()); + } + + public function testOrWhereAll() + { + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->orWhereAll(['last_name', 'email'], 'like', '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" like ? and "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->whereAll(['last_name', 'email'], 'like', '%Otwell%', 'or'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" like ? and "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->orWhereAll(['last_name', 'email'], '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" = ? and "email" = ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + } + + public function testWhereAny() + { + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereAny(['last_name', 'email'], 'like', '%Otwell%'); + $this->assertSame('select * from "users" where ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->whereAny(['last_name', 'email'], '%Otwell%'); + $this->assertSame('select * from "users" where ("last_name" = ? or "email" = ?)', $builder->toSql()); + $this->assertEquals(['%Otwell%', '%Otwell%'], $builder->getBindings()); + } + + public function testOrWhereAny() + { + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->orWhereAny(['last_name', 'email'], 'like', '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->whereAny(['last_name', 'email'], 'like', '%Otwell%', 'or'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" like ? or "email" like ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + + $builder = $this->getBuilder(); + $builder->select('*')->from('users')->where('first_name', 'like', '%Taylor%')->orWhereAny(['last_name', 'email'], '%Otwell%'); + $this->assertSame('select * from "users" where "first_name" like ? or ("last_name" = ? or "email" = ?)', $builder->toSql()); + $this->assertEquals(['%Taylor%', '%Otwell%', '%Otwell%'], $builder->getBindings()); + } + public function testUnions() { $builder = $this->getBuilder(); From 5507a19f1eb91ac4faf520e64e635145484f05ab Mon Sep 17 00:00:00 2001 From: Joe Dixon Date: Tue, 5 Mar 2024 14:54:33 +0000 Subject: [PATCH 157/325] create reverb driver (#50088) --- src/Illuminate/Broadcasting/BroadcastManager.php | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Illuminate/Broadcasting/BroadcastManager.php b/src/Illuminate/Broadcasting/BroadcastManager.php index e8bdf0bad873..192cb3b2d639 100644 --- a/src/Illuminate/Broadcasting/BroadcastManager.php +++ b/src/Illuminate/Broadcasting/BroadcastManager.php @@ -273,6 +273,17 @@ protected function callCustomCreator(array $config) return $this->customCreators[$config['driver']]($this->app, $config); } + /** + * Create an instance of the driver. + * + * @param array $config + * @return \Illuminate\Contracts\Broadcasting\Broadcaster + */ + protected function createReverbDriver(array $config) + { + return $this->createPusherDriver($config); + } + /** * Create an instance of the driver. * From c199bb8c13c902da8714a18712ea40cbf5b7efc3 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 5 Mar 2024 16:17:12 +0100 Subject: [PATCH 158/325] Update releases.yml --- .github/workflows/releases.yml | 38 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index e6177b2222a8..fe41be64b0f7 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -41,24 +41,24 @@ jobs: bash ./bin/release.sh v${{ steps.version.outputs.version }} script_stop: true - - name: Generate release notes - id: notes - uses: RedCrafter07/release-notes-action@main - with: - tag-name: v${{ steps.version.outputs.version }} - token: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.ref_name }} + # - name: Generate release notes + # id: notes + # uses: RedCrafter07/release-notes-action@main + # with: + # tag-name: v${{ steps.version.outputs.version }} + # token: ${{ secrets.GITHUB_TOKEN }} + # branch: ${{ github.ref_name }} - - name: Cleanup release notes - run: | - sed -i '/## What/d' ${{ steps.notes.outputs.release-notes }} - sed -i '/## New Contributors/,$d' ${{ steps.notes.outputs.release-notes }} + # - name: Cleanup release notes + # run: | + # sed -i '/## What/d' ${{ steps.notes.outputs.release-notes }} + # sed -i '/## New Contributors/,$d' ${{ steps.notes.outputs.release-notes }} - - name: Create release - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v${{ steps.version.outputs.version }} - name: v${{ steps.version.outputs.version }} - body: ${{ steps.notes.outputs.release-notes }} + # - name: Create release + # uses: softprops/action-gh-release@v1 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # tag_name: v${{ steps.version.outputs.version }} + # name: v${{ steps.version.outputs.version }} + # body: ${{ steps.notes.outputs.release-notes }} From fce29b8de62733cdecbe12e3bae801f83fff2ea4 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 5 Mar 2024 15:18:36 +0000 Subject: [PATCH 159/325] Update version to v10.47.0 --- 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 325bd1ccd6a4..01fc35d9f4b0 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.46.0'; + const VERSION = '10.47.0'; /** * The base path for the Laravel installation. From c4e8cf5d7e0a4a5f5621e979a07c1ed9167a6e33 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 5 Mar 2024 15:21:54 +0000 Subject: [PATCH 160/325] Update CHANGELOG --- CHANGELOG.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4866acf56e2b..7faf39f5329a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.46.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.47.0...10.x) + +## [v10.47.0](https://github.com/laravel/framework/compare/v10.46.0...v10.47.0) - 2024-03-05 + +* [10.x] Allow for relation key to be an enum by [@AJenbo](https://github.com/AJenbo) in https://github.com/laravel/framework/pull/50311 +* FIx for "empty" strings passed to Str::apa() by [@tiagof](https://github.com/tiagof) in https://github.com/laravel/framework/pull/50335 +* [10.x] Fixed header mail text component to not use markdown by [@dmyers](https://github.com/dmyers) in https://github.com/laravel/framework/pull/50332 +* [10.x] Add test for the "empty strings in `Str::apa()`" fix by [@osbre](https://github.com/osbre) in https://github.com/laravel/framework/pull/50340 +* [10.x] Fix the cache cannot expire cache with `0` TTL by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/50359 +* [10.x] Add fail on timeout to queue listener by [@saeedhosseiinii](https://github.com/saeedhosseiinii) in https://github.com/laravel/framework/pull/50352 +* [10.x] Support sort option flags on sortByMany Collections by [@TWithers](https://github.com/TWithers) in https://github.com/laravel/framework/pull/50269 +* [10.x] Add `whereAll` and `whereAny` methods to the query builder by [@musiermoore](https://github.com/musiermoore) in https://github.com/laravel/framework/pull/50344 +* [10.x] Adds Reverb broadcasting driver by [@joedixon](https://github.com/joedixon) in https://github.com/laravel/framework/pull/50088 ## [v10.46.0](https://github.com/laravel/framework/compare/v10.45.1...v10.46.0) - 2024-02-27 From d573dea384b24e830ad7ed1eb7a9cc1bb845e120 Mon Sep 17 00:00:00 2001 From: Maarten Paauw Date: Thu, 7 Mar 2024 17:33:52 +0100 Subject: [PATCH 161/325] fix: allow null, string and string array as allowed tags (#50409) * fix: allow null, string and string array as allowed tags * Update Stringable.php --------- Co-authored-by: Maarten Paauw Co-authored-by: Taylor Otwell --- src/Illuminate/Support/Stringable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Stringable.php b/src/Illuminate/Support/Stringable.php index fd344622cd3f..3a37ff11ed58 100644 --- a/src/Illuminate/Support/Stringable.php +++ b/src/Illuminate/Support/Stringable.php @@ -769,7 +769,7 @@ public function start($prefix) /** * Strip HTML and PHP tags from the given string. * - * @param string $allowedTags + * @param string[]|string|null $allowedTags * @return static */ public function stripTags($allowedTags = null) From 745b2050f63025b54950f9c93ba1e9e6073807d2 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Thu, 7 Mar 2024 17:39:23 +0100 Subject: [PATCH 162/325] Allow `Expression` at more places in Query Builder (#50402) --- src/Illuminate/Database/Query/Builder.php | 22 ++++++++++---------- src/Illuminate/Database/Query/JoinClause.php | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index f5c40f841b97..0b45dd2994f1 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -510,7 +510,7 @@ public function ignoreIndex($index) * Add a join clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @param string $type @@ -550,7 +550,7 @@ public function join($table, $first, $operator = null, $second = null, $type = ' * Add a "join where" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string $operator * @param \Illuminate\Contracts\Database\Query\Expression|string $second * @param string $type @@ -566,7 +566,7 @@ public function joinWhere($table, $first, $operator, $second, $type = 'inner') * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @param string $type @@ -623,7 +623,7 @@ public function leftJoinLateral($query, string $as) * Add a left join to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this @@ -637,7 +637,7 @@ public function leftJoin($table, $first, $operator = null, $second = null) * Add a "join where" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this @@ -652,7 +652,7 @@ public function leftJoinWhere($table, $first, $operator, $second) * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this @@ -680,7 +680,7 @@ public function rightJoin($table, $first, $operator = null, $second = null) * Add a "right join where" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string $operator * @param \Illuminate\Contracts\Database\Query\Expression|string $second * @return $this @@ -695,7 +695,7 @@ public function rightJoinWhere($table, $first, $operator, $second) * * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder|string $query * @param string $as - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this @@ -709,7 +709,7 @@ public function rightJoinSub($query, $as, $first, $operator = null, $second = nu * Add a "cross join" clause to the query. * * @param \Illuminate\Contracts\Database\Query\Expression|string $table - * @param \Closure|string|null $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string|null $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return $this @@ -1029,7 +1029,7 @@ public function orWhereNot($column, $operator = null, $value = null) /** * Add a "where" clause comparing two columns to the query. * - * @param string|array $first + * @param \Illuminate\Contracts\Database\Query\Expression|string|array $first * @param string|null $operator * @param string|null $second * @param string|null $boolean @@ -1066,7 +1066,7 @@ public function whereColumn($first, $operator = null, $second = null, $boolean = /** * Add an "or where" clause comparing two columns to the query. * - * @param string|array $first + * @param \Illuminate\Contracts\Database\Query\Expression|string|array $first * @param string|null $operator * @param string|null $second * @return $this diff --git a/src/Illuminate/Database/Query/JoinClause.php b/src/Illuminate/Database/Query/JoinClause.php index aef1c9aa547d..37a002c57245 100755 --- a/src/Illuminate/Database/Query/JoinClause.php +++ b/src/Illuminate/Database/Query/JoinClause.php @@ -82,7 +82,7 @@ public function __construct(Builder $parentQuery, $type, $table) * * on `contacts`.`user_id` = `users`.`id` and `contacts`.`info_id` = `info`.`id` * - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @param string $boolean @@ -102,7 +102,7 @@ public function on($first, $operator = null, $second = null, $boolean = 'and') /** * Add an "or on" clause to the join. * - * @param \Closure|string $first + * @param \Closure|\Illuminate\Contracts\Database\Query\Expression|string $first * @param string|null $operator * @param \Illuminate\Contracts\Database\Query\Expression|string|null $second * @return \Illuminate\Database\Query\JoinClause From f43ad93d30c8932fdf7db3b9f7ab96608b9beb26 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Fri, 8 Mar 2024 03:58:20 +1100 Subject: [PATCH 163/325] [10.x] Sleep syncing (#50392) * Use support carbon * Allow syncing of carbon with sleep * Fix capitalization --- src/Illuminate/Support/Sleep.php | 24 +++++++++++++++++++++++- tests/Support/SleepTest.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Sleep.php b/src/Illuminate/Support/Sleep.php index cd32d7f479c5..f54ec2a45cf8 100644 --- a/src/Illuminate/Support/Sleep.php +++ b/src/Illuminate/Support/Sleep.php @@ -2,9 +2,9 @@ namespace Illuminate\Support; -use Carbon\Carbon; use Carbon\CarbonInterval; use DateInterval; +use Illuminate\Support\Carbon; use Illuminate\Support\Traits\Macroable; use PHPUnit\Framework\Assert as PHPUnit; use RuntimeException; @@ -20,6 +20,13 @@ class Sleep */ public static $fakeSleepCallbacks = []; + /** + * Keep Carbon's "now" in sync when sleeping. + * + * @var bool + */ + protected static $syncWithCarbon = false; + /** * The total duration to sleep. * @@ -259,6 +266,10 @@ public function __destruct() if (static::$fake) { static::$sequence[] = $this->duration; + if (static::$syncWithCarbon) { + Carbon::setTestNow(Carbon::now()->add($this->duration)); + } + foreach (static::$fakeSleepCallbacks as $callback) { $callback($this->duration); } @@ -317,6 +328,7 @@ public static function fake($value = true) static::$sequence = []; static::$fakeSleepCallbacks = []; + static::$syncWithCarbon = false; } /** @@ -458,4 +470,14 @@ public static function whenFakingSleep($callback) { static::$fakeSleepCallbacks[] = $callback; } + + /** + * Indicate that Carbon's "now" should be kept in sync when sleeping. + * + * @return void + */ + public static function syncWithCarbon($value = true) + { + static::$syncWithCarbon = $value; + } } diff --git a/tests/Support/SleepTest.php b/tests/Support/SleepTest.php index d43b96a0d7f7..6afbd9114ec9 100644 --- a/tests/Support/SleepTest.php +++ b/tests/Support/SleepTest.php @@ -5,6 +5,7 @@ use Carbon\CarbonInterval; use Exception; use Illuminate\Support\Carbon; +use Illuminate\Support\Facades\Date; use Illuminate\Support\Sleep; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; @@ -563,4 +564,33 @@ public function testItDoesntRunCallbacksWhenNotFaking() $this->assertTrue(true); } + + public function testItDoesNotSyncCarbon() + { + Carbon::setTestNow('2000-01-01 00:00:00'); + Sleep::fake(); + + Sleep::for(5)->minutes() + ->and(3)->seconds(); + + Sleep::assertSequence([ + Sleep::for(303)->seconds(), + ]); + $this->assertSame('2000-01-01 00:00:00', Date::now()->toDateTimeString()); + } + + public function testItCanSyncCarbon() + { + Carbon::setTestNow('2000-01-01 00:00:00'); + Sleep::fake(); + Sleep::syncWithCarbon(); + + Sleep::for(5)->minutes() + ->and(3)->seconds(); + + Sleep::assertSequence([ + Sleep::for(303)->seconds(), + ]); + $this->assertSame('2000-01-01 00:05:03', Date::now()->toDateTimeString()); + } } From cebd8d93d722bbf44f75bdecc8c097d2f6701b96 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Thu, 7 Mar 2024 16:58:40 +0000 Subject: [PATCH 164/325] Apply fixes from StyleCI --- src/Illuminate/Support/Sleep.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Illuminate/Support/Sleep.php b/src/Illuminate/Support/Sleep.php index f54ec2a45cf8..f52fb3e4bfc8 100644 --- a/src/Illuminate/Support/Sleep.php +++ b/src/Illuminate/Support/Sleep.php @@ -4,7 +4,6 @@ use Carbon\CarbonInterval; use DateInterval; -use Illuminate\Support\Carbon; use Illuminate\Support\Traits\Macroable; use PHPUnit\Framework\Assert as PHPUnit; use RuntimeException; From 6c27ca43dbcfd232813ca5aba8ad3c80405ba9b1 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Fri, 8 Mar 2024 03:31:57 +0100 Subject: [PATCH 165/325] fix: trait on the same line (#50413) --- src/Illuminate/Cache/Repository.php | 3 +-- src/Illuminate/Filesystem/Filesystem.php | 3 +-- src/Illuminate/Validation/Rules/File.php | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Cache/Repository.php b/src/Illuminate/Cache/Repository.php index 44f3181f3f4a..606f73b3171c 100755 --- a/src/Illuminate/Cache/Repository.php +++ b/src/Illuminate/Cache/Repository.php @@ -22,8 +22,7 @@ */ class Repository implements ArrayAccess, CacheContract { - use InteractsWithTime; - use Macroable { + use InteractsWithTime, Macroable { __call as macroCall; } diff --git a/src/Illuminate/Filesystem/Filesystem.php b/src/Illuminate/Filesystem/Filesystem.php index c9485337c299..3c464fd00b66 100644 --- a/src/Illuminate/Filesystem/Filesystem.php +++ b/src/Illuminate/Filesystem/Filesystem.php @@ -16,8 +16,7 @@ class Filesystem { - use Conditionable; - use Macroable; + use Conditionable, Macroable; /** * Determine if a file or directory exists. diff --git a/src/Illuminate/Validation/Rules/File.php b/src/Illuminate/Validation/Rules/File.php index fb9a0603c2d6..e93e1759df43 100644 --- a/src/Illuminate/Validation/Rules/File.php +++ b/src/Illuminate/Validation/Rules/File.php @@ -14,8 +14,7 @@ class File implements Rule, DataAwareRule, ValidatorAwareRule { - use Conditionable; - use Macroable; + use Conditionable, Macroable; /** * The MIME types that the given file should match. This array may also contain file extensions. From a03b17c85ea9436f331d8ee9e637a621bb870194 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20JEAN?= Date: Fri, 8 Mar 2024 17:14:24 +0100 Subject: [PATCH 166/325] fix: incomplete type for Builder::from property (#50426) * fix: incomplete type for Builder::from property * Update Builder.php --------- Co-authored-by: Taylor Otwell --- 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 0b45dd2994f1..6632aa3bf79c 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -98,7 +98,7 @@ class Builder implements BuilderContract /** * The table which the query is targeting. * - * @var string + * @var \Illuminate\Database\Query\Expression|string */ public $from; From bbb2add1bd68e312239fe264fd68fe6ad42ae0aa Mon Sep 17 00:00:00 2001 From: Oleksandr Prypkhan Date: Fri, 8 Mar 2024 22:24:57 +0200 Subject: [PATCH 167/325] [10.x] After commit callback throwing an exception causes broken transactions afterwards (#50423) * Fix after commit callback throwing an exception causing the transaction level to be incorrect * Extract level being committed --- .../Database/Concerns/ManagesTransactions.php | 19 ++++--- ...loquentTransactionWithAfterCommitTests.php | 52 +++++++++++++++++++ 2 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Database/Concerns/ManagesTransactions.php b/src/Illuminate/Database/Concerns/ManagesTransactions.php index df60c61b6d87..ce0342ec5010 100644 --- a/src/Illuminate/Database/Concerns/ManagesTransactions.php +++ b/src/Illuminate/Database/Concerns/ManagesTransactions.php @@ -41,22 +41,15 @@ public function transaction(Closure $callback, $attempts = 1) continue; } + $levelBeingCommitted = $this->transactions; + try { if ($this->transactions == 1) { $this->fireConnectionEvent('committing'); $this->getPdo()->commit(); } - [$levelBeingCommitted, $this->transactions] = [ - $this->transactions, - max(0, $this->transactions - 1), - ]; - - $this->transactionsManager?->commit( - $this->getName(), - $levelBeingCommitted, - $this->transactions - ); + $this->transactions = max(0, $this->transactions - 1); } catch (Throwable $e) { $this->handleCommitTransactionException( $e, $currentAttempt, $attempts @@ -65,6 +58,12 @@ public function transaction(Closure $callback, $attempts = 1) continue; } + $this->transactionsManager?->commit( + $this->getName(), + $levelBeingCommitted, + $this->transactions + ); + $this->fireConnectionEvent('committed'); return $callbackResult; diff --git a/tests/Integration/Database/EloquentTransactionWithAfterCommitTests.php b/tests/Integration/Database/EloquentTransactionWithAfterCommitTests.php index 262ef43d9c5d..e353d0ef099c 100644 --- a/tests/Integration/Database/EloquentTransactionWithAfterCommitTests.php +++ b/tests/Integration/Database/EloquentTransactionWithAfterCommitTests.php @@ -101,6 +101,45 @@ public function testObserverIsCalledEvenWhenDeeplyNestingTransactions() $this->assertTrue($user1->exists); $this->assertEquals(1, $observer::$calledTimes, 'Failed to assert the observer was called once.'); } + + public function testTransactionCallbackExceptions() + { + [$firstObject, $secondObject] = [ + new EloquentTransactionWithAfterCommitTestsTestObjectForTransactions(), + new EloquentTransactionWithAfterCommitTestsTestObjectForTransactions(), + ]; + + $rootTransactionLevel = DB::transactionLevel(); + + // After commit callbacks may fail with an exception. When they do, the rest of the callbacks are not + // executed. It's important that the transaction would already be committed by that point, so the + // transaction level should be modified before executing any callbacks. Also, exceptions in the + // callbacks should not affect the connection's transaction level. + $this->assertThrows(function () use ($rootTransactionLevel, $secondObject, $firstObject) { + DB::transaction(function () use ($rootTransactionLevel, $firstObject, $secondObject) { + DB::transaction(function () use ($rootTransactionLevel, $firstObject) { + $this->assertSame($rootTransactionLevel + 2, DB::transactionLevel()); + + DB::afterCommit(function () use ($rootTransactionLevel, $firstObject) { + $this->assertSame($rootTransactionLevel, DB::transactionLevel()); + + $firstObject->handle(); + }); + }); + + $this->assertSame($rootTransactionLevel + 1, DB::transactionLevel()); + + DB::afterCommit(fn () => throw new \RuntimeException()); + DB::afterCommit(fn () => $secondObject->handle()); + }); + }, \RuntimeException::class); + + $this->assertSame($rootTransactionLevel, DB::transactionLevel()); + + $this->assertTrue($firstObject->ran); + $this->assertFalse($secondObject->ran); + $this->assertEquals(1, $firstObject->runs); + } } class EloquentTransactionWithAfterCommitTestsUserObserver @@ -150,3 +189,16 @@ public function handle(): void }); } } + +class EloquentTransactionWithAfterCommitTestsTestObjectForTransactions +{ + public $ran = false; + + public $runs = 0; + + public function handle() + { + $this->ran = true; + $this->runs++; + } +} From f2bff1c04ee0eb978d2daef4fdc48d27e23f7bf5 Mon Sep 17 00:00:00 2001 From: Dan Harrin Date: Fri, 8 Mar 2024 20:46:22 +0000 Subject: [PATCH 168/325] [10.x] Anonymous component bound attribute values are evaluated twice (#50403) * fix: Anonymous component attributes evaluated multiple times * Update tests to use new compiled code * Fix edge case with attribute bag and refactor * Update ComponentTagCompiler.php * Update BladeComponentsTest.php * Update ComponentTagCompiler.php * Update ComponentTagCompiler.php * use loop --------- Co-authored-by: Taylor Otwell --- .../View/Compilers/ComponentTagCompiler.php | 33 ++++++- .../Compilers/Concerns/CompilesComponents.php | 5 ++ .../Blade/BladeComponentTagCompilerTest.php | 86 +++++++++---------- tests/View/Blade/BladeComponentsTest.php | 5 ++ 4 files changed, 84 insertions(+), 45 deletions(-) diff --git a/src/Illuminate/View/Compilers/ComponentTagCompiler.php b/src/Illuminate/View/Compilers/ComponentTagCompiler.php index 8b360762127e..02bd4b7b1003 100644 --- a/src/Illuminate/View/Compilers/ComponentTagCompiler.php +++ b/src/Illuminate/View/Compilers/ComponentTagCompiler.php @@ -258,11 +258,11 @@ protected function componentString(string $component, array $attributes) $parameters = $data->all(); } - return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', [".$this->attributesToString($parameters, $escapeBound = false).']) + return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', \$componentData = [".$this->attributesToString($parameters, $escapeBound = false).']) getConstructor()): ?> except(collect($constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['.$this->attributesToString($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class).']); ?>'; +withAttributes(['.$this->attributesToStringWithExistingComponentData($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class).']); ?>'; } /** @@ -783,6 +783,35 @@ protected function attributesToString(array $attributes, $escapeBound = true) ->implode(','); } + /** + * Convert an array of attributes to a string using existing component data that has already been evaluated. + * + * @param array $attributes + * @param bool $escapeBound + * @return string + */ + protected function attributesToStringWithExistingComponentData(array $attributes, $escapeBound = true) + { + $results = []; + + foreach ($attributes as $attribute => $value) { + if (! $escapeBound || + ! isset($this->boundAttributes[$attribute]) || + $value === 'true' || + is_numeric($value)) { + $results[] = "'{$attribute}' => {$value}"; + + continue; + } + + $results[] = "'{$attribute}' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(".( + ($attribute === 'attributes') ? $value : ('$componentData[\'data\'][\''.Str::camel($attribute).'\'] ?? null') + ).')'; + } + + return implode(',', $results); + } + /** * Strip any quotes from the given string. * diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php b/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php index 88617186fb20..5884f3b18dba 100644 --- a/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php +++ b/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php @@ -67,6 +67,7 @@ public static function compileClassComponentOpening(string $component, string $a { return implode("\n", [ '', + '', '', 'getIterator() : [])); ?>', 'withName('.$alias.'); ?>', @@ -100,6 +101,10 @@ public function compileEndComponentClass() '', '', '', + '', + '', + '', + '', '', '', '', diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php index 5143ae503c72..0335f216e96e 100644 --- a/tests/View/Blade/BladeComponentTagCompilerTest.php +++ b/tests/View/Blade/BladeComponentTagCompilerTest.php @@ -102,12 +102,12 @@ public function testBasicComponentParsing() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> withAttributes(['type' => 'foo','limit' => '5','@click' => 'foo','wire:click' => 'changePlan(\''.e(\$plan).'\')','required' => true,'x-intersect.margin.-50%.0px' => 'visibleSection = \'profile\'']); ?>\n". -"@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) +"@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -120,7 +120,7 @@ public function testBasicComponentWithEmptyAttributesParsing() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -133,7 +133,7 @@ public function testDataCamelCasing() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => '1']) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => '1']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -145,7 +145,7 @@ public function testColonData() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => 1]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -157,7 +157,7 @@ public function testColonDataShortSyntax() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => \$userId]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -169,7 +169,7 @@ public function testColonDataWithStaticClassProperty() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => User::\$id]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -181,7 +181,7 @@ public function testColonDataWithStaticClassPropertyAndMultipleAttributes() $this->mockViewFactory(); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', \$componentData = ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -189,7 +189,7 @@ public function testColonDataWithStaticClassPropertyAndMultipleAttributes() $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', \$componentData = ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -201,7 +201,7 @@ public function testSelfClosingComponentWithColonDataShortSyntax() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => \$userId]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -214,7 +214,7 @@ public function testSelfClosingComponentWithColonDataAndStaticClassPropertyShort $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => User::\$id]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -227,7 +227,7 @@ public function testSelfClosingComponentWithColonDataMultipleAttributesAndStatic $this->mockViewFactory(); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', \$componentData = ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -236,7 +236,7 @@ public function testSelfClosingComponentWithColonDataMultipleAttributesAndStatic $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', \$componentData = ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -249,7 +249,7 @@ public function testEscapedColonAttribute() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => 1]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -261,11 +261,11 @@ public function testColonAttributesIsEscapedIfStrings() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['src' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('foo')]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['src' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['src'] ?? null)]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testClassDirective() @@ -273,11 +273,11 @@ public function testClassDirective() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['class'] ?? null)]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testStyleDirective() @@ -285,11 +285,11 @@ public function testStyleDirective() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['style'] ?? null)]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testColonNestedComponentParsing() @@ -297,7 +297,7 @@ public function testColonNestedComponentParsing() $this->mockViewFactory(); $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -309,7 +309,7 @@ public function testColonStartingNestedComponentParsing() $this->mockViewFactory(); $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -321,7 +321,7 @@ public function testSelfClosingComponentsCanBeCompiled() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -363,7 +363,7 @@ public function testComponentsCanBeCompiledWithHyphenAttributes() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -376,7 +376,7 @@ public function testSelfClosingComponentsCanBeCompiledWithDataAndAttributes() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = ['title' => 'foo']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -390,7 +390,7 @@ public function testComponentCanReceiveAttributeBag() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -403,7 +403,7 @@ public function testSelfClosingComponentCanReceiveAttributeBag() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
merge([\'class\' => \'test\']) }} wire:model="foo" />
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = ['title' => 'foo']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -416,7 +416,7 @@ public function testComponentsCanHaveAttachedWord() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('Words'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -428,7 +428,7 @@ public function testSelfClosingComponentsCanHaveAttachedWord() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('Words'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -441,7 +441,7 @@ public function testSelfClosingComponentsCanBeCompiledWithBoundData() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => \$title]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = ['title' => \$title]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -455,7 +455,7 @@ public function testPairedComponentTags() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(' '); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -474,11 +474,11 @@ public function testClasslessComponents() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', \$componentData = ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -493,11 +493,11 @@ public function testClasslessComponentsWithIndexView() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', \$componentData = ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -512,11 +512,11 @@ public function testPackagesClasslessComponents() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'package::anonymous-component', \$componentData = ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -546,11 +546,11 @@ public function testClasslessComponentsWithAnonymousComponentNamespace() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'frontend::anonymous-component', ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'frontend::anonymous-component', \$componentData = ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -580,11 +580,11 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexV $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', \$componentData = ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -613,7 +613,7 @@ public function testClasslessComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.index','data' => []]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', \$componentData = ['view' => '".md5('test-directory')."::panel.index','data' => []]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -646,7 +646,7 @@ public function testClasslessIndexComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel','data' => []]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', \$componentData = ['view' => '".md5('test-directory')."::panel','data' => []]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> diff --git a/tests/View/Blade/BladeComponentsTest.php b/tests/View/Blade/BladeComponentsTest.php index 2615edf91452..95bc0f8a5825 100644 --- a/tests/View/Blade/BladeComponentsTest.php +++ b/tests/View/Blade/BladeComponentsTest.php @@ -17,6 +17,7 @@ public function testComponentsAreCompiled() public function testClassComponentsAreCompiled() { $this->assertSame(' + "bar"] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? (array) $attributes->getIterator() : [])); ?> withName(\'test\'); ?> @@ -41,6 +42,10 @@ public function testEndComponentClassesAreCompiled() + + + + From 24d7bd274e3332e8c7248f9ada43879ccbbd5ef6 Mon Sep 17 00:00:00 2001 From: Tim Withers Date: Sun, 10 Mar 2024 08:34:39 -0700 Subject: [PATCH 169/325] [10.x] Fix for sortByDesc ignoring multiple attributes (#50431) * Failing Test * Fix for sortByDesc * Update Collection.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/Collection.php | 10 ++++++++++ tests/Support/SupportCollectionTest.php | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 5716847138b8..0bbbe189816f 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1490,6 +1490,16 @@ protected function sortByMany(array $comparisons = [], int $options = SORT_REGUL */ public function sortByDesc($callback, $options = SORT_REGULAR) { + if (is_array($callback) && ! is_callable($callback)) { + foreach ($callback as $index => $key) { + $comparison = Arr::wrap($key); + + $comparison[1] = 'desc'; + + $callback[$index] = $comparison; + } + } + return $this->sortBy($callback, $options, true); } diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index eea0bde94c42..7c8d47a0d4ea 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -2047,6 +2047,21 @@ public function testSortByCallableString($collection) $this->assertEquals([['sort' => 1], ['sort' => 2]], array_values($data->all())); } + #[DataProvider('collectionClassProvider')] + public function testSortByCallableStringDesc($collection) + { + $data = new $collection([['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar']]); + $data = $data->sortByDesc(['id']); + $this->assertEquals([['id' => 2, 'name' => 'bar'], ['id' => 1, 'name' => 'foo']], array_values($data->all())); + + $data = new $collection([['id' => 1, 'name' => 'foo'], ['id' => 2, 'name' => 'bar'], ['id' => 2, 'name' => 'baz']]); + $data = $data->sortByDesc(['id']); + $this->assertEquals([['id' => 2, 'name' => 'bar'], ['id' => 2, 'name' => 'baz'], ['id' => 1, 'name' => 'foo']], array_values($data->all())); + + $data = $data->sortByDesc(['id', 'name']); + $this->assertEquals([['id' => 2, 'name' => 'baz'], ['id' => 2, 'name' => 'bar'], ['id' => 1, 'name' => 'foo']], array_values($data->all())); + } + /** * @dataProvider collectionClassProvider */ From 3c3d6a66f2d5dff20bd7ad4e8dc82e94f748d5ff Mon Sep 17 00:00:00 2001 From: Abenet Tamiru Date: Mon, 11 Mar 2024 13:55:41 +0100 Subject: [PATCH 170/325] [10.x] Allow sync with carbon to be set from fake method (#50450) * feat: allow sync with carbon to be set from fake method * style: add trailing commas --- src/Illuminate/Support/Sleep.php | 5 +++-- tests/Support/SleepTest.php | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/Sleep.php b/src/Illuminate/Support/Sleep.php index f52fb3e4bfc8..54cefe6334b9 100644 --- a/src/Illuminate/Support/Sleep.php +++ b/src/Illuminate/Support/Sleep.php @@ -319,15 +319,16 @@ protected function pullPending() * Stay awake and capture any attempts to sleep. * * @param bool $value + * @param bool $syncWithCarbon * @return void */ - public static function fake($value = true) + public static function fake($value = true, $syncWithCarbon = false) { static::$fake = $value; static::$sequence = []; static::$fakeSleepCallbacks = []; - static::$syncWithCarbon = false; + static::$syncWithCarbon = $syncWithCarbon; } /** diff --git a/tests/Support/SleepTest.php b/tests/Support/SleepTest.php index 6afbd9114ec9..957d3c20c99e 100644 --- a/tests/Support/SleepTest.php +++ b/tests/Support/SleepTest.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\Date; use Illuminate\Support\Sleep; use PHPUnit\Framework\AssertionFailedError; +use PHPUnit\Framework\Attributes\TestWith; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -593,4 +594,40 @@ public function testItCanSyncCarbon() ]); $this->assertSame('2000-01-01 00:05:03', Date::now()->toDateTimeString()); } + + #[TestWith([ + 'syncWithCarbon' => true, + 'datetime' => '2000-01-01 00:05:03', + ])] + #[TestWith([ + 'syncWithCarbon' => false, + 'datetime' => '2000-01-01 00:00:00', + ])] + public function testFakeCanSetSyncWithCarbon(bool $syncWithCarbon, string $datetime) + { + Carbon::setTestNow('2000-01-01 00:00:00'); + Sleep::fake(syncWithCarbon: $syncWithCarbon); + + Sleep::for(5)->minutes() + ->and(3)->seconds(); + + Sleep::assertSequence([ + Sleep::for(303)->seconds(), + ]); + $this->assertSame($datetime, Date::now()->toDateTimeString()); + } + + public function testFakeDoesNotNeedToSyncWithCarbon() + { + Carbon::setTestNow('2000-01-01 00:00:00'); + Sleep::fake(); + + Sleep::for(5)->minutes() + ->and(3)->seconds(); + + Sleep::assertSequence([ + Sleep::for(303)->seconds(), + ]); + $this->assertSame('2000-01-01 00:00:00', Date::now()->toDateTimeString()); + } } From a518ef66bebd04c97fa1f04cb3a450795cc3ca11 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 11 Mar 2024 20:56:26 +0800 Subject: [PATCH 171/325] [10.x] Improves `Illuminate\Mail\Mailables\Envelope` docblock (#50448) fixes #50430 Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Mail/Mailables/Envelope.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Mail/Mailables/Envelope.php b/src/Illuminate/Mail/Mailables/Envelope.php index 7d6c4b1ee55f..c5126f5ff2e4 100644 --- a/src/Illuminate/Mail/Mailables/Envelope.php +++ b/src/Illuminate/Mail/Mailables/Envelope.php @@ -77,10 +77,10 @@ class Envelope * Create a new message envelope instance. * * @param \Illuminate\Mail\Mailables\Address|string|null $from - * @param array $to - * @param array $cc - * @param array $bcc - * @param array $replyTo + * @param array $to + * @param array $cc + * @param array $bcc + * @param array $replyTo * @param string|null $subject * @param array $tags * @param array $metadata @@ -105,8 +105,8 @@ public function __construct(Address|string $from = null, $to = [], $cc = [], $bc /** * Normalize the given array of addresses. * - * @param array $addresses - * @return array + * @param array $addresses + * @return array */ protected function normalizeAddresses($addresses) { @@ -132,7 +132,7 @@ public function from(Address|string $address, $name = null) /** * Add a "to" recipient to the message envelope. * - * @param \Illuminate\Mail\Mailables\Address|array|string $address + * @param \Illuminate\Mail\Mailables\Address|array|string $address * @param string|null $name * @return $this */ @@ -148,7 +148,7 @@ public function to(Address|array|string $address, $name = null) /** * Add a "cc" recipient to the message envelope. * - * @param \Illuminate\Mail\Mailables\Address|array|string $address + * @param \Illuminate\Mail\Mailables\Address|array|string $address * @param string|null $name * @return $this */ @@ -164,7 +164,7 @@ public function cc(Address|array|string $address, $name = null) /** * Add a "bcc" recipient to the message envelope. * - * @param \Illuminate\Mail\Mailables\Address|array|string $address + * @param \Illuminate\Mail\Mailables\Address|array|string $address * @param string|null $name * @return $this */ @@ -180,7 +180,7 @@ public function bcc(Address|array|string $address, $name = null) /** * Add a "reply to" recipient to the message envelope. * - * @param \Illuminate\Mail\Mailables\Address|array|string $address + * @param \Illuminate\Mail\Mailables\Address|array|string $address * @param string|null $name * @return $this */ From b1224ee6a181186519f024bc7a03e0ecc2781af0 Mon Sep 17 00:00:00 2001 From: Guillaume Date: Mon, 11 Mar 2024 22:45:53 +0100 Subject: [PATCH 172/325] [10.x] Incorrect return in `FileSystem.php` (#50459) * [10.x] incorrect return * Update Filesystem.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Filesystem/Filesystem.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Filesystem/Filesystem.php b/src/Illuminate/Filesystem/Filesystem.php index 3c464fd00b66..cfd4c2207835 100644 --- a/src/Illuminate/Filesystem/Filesystem.php +++ b/src/Illuminate/Filesystem/Filesystem.php @@ -347,7 +347,7 @@ public function copy($path, $target) * * @param string $target * @param string $link - * @return void + * @return bool|null */ public function link($target, $link) { From 2614d30175257a0d6ce97893355f5f8c5c9c44f0 Mon Sep 17 00:00:00 2001 From: Mahmood Dehghani Date: Tue, 12 Mar 2024 01:16:09 +0330 Subject: [PATCH 173/325] fix return types (#50461) --- src/Illuminate/Console/Scheduling/ScheduleRunCommand.php | 2 +- .../Foundation/Bootstrap/LoadEnvironmentVariables.php | 2 +- src/Illuminate/Queue/Listener.php | 2 +- src/Illuminate/Routing/Route.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php index f6386b89d48b..917ff9a183b1 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php @@ -271,7 +271,7 @@ protected function shouldInterrupt() /** * Ensure the interrupt signal is cleared. * - * @return bool + * @return void */ protected function clearInterruptSignal() { diff --git a/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php b/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php index 3f0be6c0a605..050a96967e04 100644 --- a/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php +++ b/src/Illuminate/Foundation/Bootstrap/LoadEnvironmentVariables.php @@ -94,7 +94,7 @@ protected function createDotenv($app) * Write the error information to the screen and exit. * * @param \Dotenv\Exception\InvalidFileException $e - * @return void + * @return never */ protected function writeErrorAndDie(InvalidFileException $e) { diff --git a/src/Illuminate/Queue/Listener.php b/src/Illuminate/Queue/Listener.php index b552828f167f..f7744b45bb01 100755 --- a/src/Illuminate/Queue/Listener.php +++ b/src/Illuminate/Queue/Listener.php @@ -216,7 +216,7 @@ public function memoryExceeded($memoryLimit) /** * Stop listening and bail out of the script. * - * @return void + * @return never */ public function stop() { diff --git a/src/Illuminate/Routing/Route.php b/src/Illuminate/Routing/Route.php index 02518733300c..86e808a9446f 100755 --- a/src/Illuminate/Routing/Route.php +++ b/src/Illuminate/Routing/Route.php @@ -577,7 +577,7 @@ public function setBindingFields(array $bindingFields) * Get the parent parameter of the given parameter. * * @param string $parameter - * @return string + * @return string|null */ public function parentOfParameter($parameter) { From 5a7f633f8a4537bd48382f5442b809f4284e3bdd Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Mon, 11 Mar 2024 21:46:45 +0000 Subject: [PATCH 174/325] Update facade docblocks --- src/Illuminate/Support/Facades/File.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/File.php b/src/Illuminate/Support/Facades/File.php index a1ade8789643..fd43b658e387 100755 --- a/src/Illuminate/Support/Facades/File.php +++ b/src/Illuminate/Support/Facades/File.php @@ -21,7 +21,7 @@ * @method static bool delete(string|array $paths) * @method static bool move(string $path, string $target) * @method static bool copy(string $path, string $target) - * @method static void link(string $target, string $link) + * @method static bool|null link(string $target, string $link) * @method static void relativeLink(string $target, string $link) * @method static string name(string $path) * @method static string basename(string $path) From f1fb9a615849ccf72bb75c90773295bbff17a2f6 Mon Sep 17 00:00:00 2001 From: Filippo Montani <78483736+Carnicero90@users.noreply.github.com> Date: Tue, 12 Mar 2024 03:39:22 +0100 Subject: [PATCH 175/325] fix: phpstan issue - right side of || always false (#50453) --- src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php | 2 +- src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php b/src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php index f5f0571bca50..7909b197b87b 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEnumArrayObject.php @@ -30,7 +30,7 @@ public function __construct(array $arguments) public function get($model, $key, $value, $attributes) { - if (! isset($attributes[$key]) || is_null($attributes[$key])) { + if (! isset($attributes[$key])) { return; } diff --git a/src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php b/src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php index ff632ed223cd..926881287ef7 100644 --- a/src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php +++ b/src/Illuminate/Database/Eloquent/Casts/AsEnumCollection.php @@ -30,7 +30,7 @@ public function __construct(array $arguments) public function get($model, $key, $value, $attributes) { - if (! isset($attributes[$key]) || is_null($attributes[$key])) { + if (! isset($attributes[$key])) { return; } From e83521e3d049a629b28b85f05592ef0f6b86eb34 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 12 Mar 2024 12:51:32 +0000 Subject: [PATCH 176/325] Update version to v10.48.0 --- 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 01fc35d9f4b0..514e35d0ba2c 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.47.0'; + const VERSION = '10.48.0'; /** * The base path for the Laravel installation. From f42f5f4cbfb4ee18223355de09a7db1a7683360a Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 12 Mar 2024 12:53:27 +0000 Subject: [PATCH 177/325] Update CHANGELOG --- CHANGELOG.md | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7faf39f5329a..cc20bbcd2d5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.47.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.0...10.x) + +## [v10.48.0](https://github.com/laravel/framework/compare/v10.47.0...v10.48.0) - 2024-03-12 + +* fix: allow null, string and string array as allowed tags by [@maartenpaauw](https://github.com/maartenpaauw) in https://github.com/laravel/framework/pull/50409 +* [10.x] Allow `Expression` at more places in Query Builder by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/laravel/framework/pull/50402 +* [10.x] Sleep syncing by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/50392 +* [10.x] Cleaning Trait on multi-lines by [@gcazin](https://github.com/gcazin) in https://github.com/laravel/framework/pull/50413 +* fix: incomplete type for Builder::from property by [@sebj54](https://github.com/sebj54) in https://github.com/laravel/framework/pull/50426 +* [10.x] After commit callback throwing an exception causes broken transactions afterwards by [@oprypkhantc](https://github.com/oprypkhantc) in https://github.com/laravel/framework/pull/50423 +* [10.x] Anonymous component bound attribute values are evaluated twice by [@danharrin](https://github.com/danharrin) in https://github.com/laravel/framework/pull/50403 +* [10.x] Fix for sortByDesc ignoring multiple attributes by [@TWithers](https://github.com/TWithers) in https://github.com/laravel/framework/pull/50431 +* [10.x] Allow sync with carbon to be set from fake method by [@abenerd](https://github.com/abenerd) in https://github.com/laravel/framework/pull/50450 +* [10.x] Improves `Illuminate\Mail\Mailables\Envelope` docblock by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/50448 +* [10.x] Incorrect return in `FileSystem.php` by [@gcazin](https://github.com/gcazin) in https://github.com/laravel/framework/pull/50459 +* [10.x] fix return types by [@imahmood](https://github.com/imahmood) in https://github.com/laravel/framework/pull/50461 +* fix: phpstan issue - right side of || always false by [@Carnicero90](https://github.com/Carnicero90) in https://github.com/laravel/framework/pull/50453 ## [v10.47.0](https://github.com/laravel/framework/compare/v10.46.0...v10.47.0) - 2024-03-05 From 48cf0e692e87784666b90110c49d13ceda85f58e Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 12 Mar 2024 14:40:15 +0100 Subject: [PATCH 178/325] Add conflict for Mockery v1.6.8 (#50468) --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index c2559dd1af64..9dcb6080f1ea 100644 --- a/composer.json +++ b/composer.json @@ -121,6 +121,7 @@ "conflict": { "carbonphp/carbon-doctrine-types": ">=3.0", "doctrine/dbal": ">=4.0", + "mockery/mockery": ">=1.6.8", "tightenco/collect": "<5.5.33", "phpunit/phpunit": ">=11.0.0" }, From 03c9b53e4b740e0486db8eb2569a5e8032843ebf Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 12 Mar 2024 13:41:54 +0000 Subject: [PATCH 179/325] Update version to v10.48.1 --- 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 514e35d0ba2c..ed1c4b83e86e 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.0'; + const VERSION = '10.48.1'; /** * The base path for the Laravel installation. From 5bbd867de0fb45c2501dc420516f49f8bbe57766 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 12 Mar 2024 13:46:02 +0000 Subject: [PATCH 180/325] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc20bbcd2d5a..a403b499219f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.0...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.1...10.x) + +## [v10.48.1](https://github.com/laravel/framework/compare/v10.48.0...v10.48.1) - 2024-03-12 + +* [10.x] Add conflict for Mockery v1.6.8 by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50468 ## [v10.48.0](https://github.com/laravel/framework/compare/v10.47.0...v10.48.0) - 2024-03-12 From 089c593bd8c69a91bb5b0543e71142ded39bc0e1 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Tue, 12 Mar 2024 14:36:29 +0000 Subject: [PATCH 181/325] Update mockery conflict to just disallow the broken version (#50472) --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 9dcb6080f1ea..f4abfd3b3f8d 100644 --- a/composer.json +++ b/composer.json @@ -121,7 +121,7 @@ "conflict": { "carbonphp/carbon-doctrine-types": ">=3.0", "doctrine/dbal": ">=4.0", - "mockery/mockery": ">=1.6.8", + "mockery/mockery": "1.6.8", "tightenco/collect": "<5.5.33", "phpunit/phpunit": ">=11.0.0" }, From b2535c19c26e4e665a707fd8e16226f7488d97b2 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 12 Mar 2024 15:36:40 +0100 Subject: [PATCH 182/325] Conflict with specific release (#50473) From 40c32ce124900b801b8693f65e37c67c8f4747e1 Mon Sep 17 00:00:00 2001 From: Pascal Baljet Date: Tue, 12 Mar 2024 15:37:11 +0100 Subject: [PATCH 183/325] Check for subclasses of `DynamicComponent` (#50471) --- src/Illuminate/View/Compilers/ComponentTagCompiler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/View/Compilers/ComponentTagCompiler.php b/src/Illuminate/View/Compilers/ComponentTagCompiler.php index 02bd4b7b1003..c87ddc4f949c 100644 --- a/src/Illuminate/View/Compilers/ComponentTagCompiler.php +++ b/src/Illuminate/View/Compilers/ComponentTagCompiler.php @@ -262,7 +262,7 @@ protected function componentString(string $component, array $attributes) getConstructor()): ?> except(collect($constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['.$this->attributesToStringWithExistingComponentData($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class).']); ?>'; +withAttributes(['.$this->attributesToStringWithExistingComponentData($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class && ! is_subclass_of($class, DynamicComponent::class)).']); ?>'; } /** From 199193aaeba467ce12f848961af90541ebfb1433 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 12 Mar 2024 17:33:42 +0100 Subject: [PATCH 184/325] Revert PR 50403 (#50482) --- .../View/Compilers/ComponentTagCompiler.php | 33 +------ .../Compilers/Concerns/CompilesComponents.php | 5 -- .../Blade/BladeComponentTagCompilerTest.php | 86 +++++++++---------- tests/View/Blade/BladeComponentsTest.php | 5 -- 4 files changed, 45 insertions(+), 84 deletions(-) diff --git a/src/Illuminate/View/Compilers/ComponentTagCompiler.php b/src/Illuminate/View/Compilers/ComponentTagCompiler.php index c87ddc4f949c..8b360762127e 100644 --- a/src/Illuminate/View/Compilers/ComponentTagCompiler.php +++ b/src/Illuminate/View/Compilers/ComponentTagCompiler.php @@ -258,11 +258,11 @@ protected function componentString(string $component, array $attributes) $parameters = $data->all(); } - return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', \$componentData = [".$this->attributesToString($parameters, $escapeBound = false).']) + return "##BEGIN-COMPONENT-CLASS##@component('{$class}', '{$component}', [".$this->attributesToString($parameters, $escapeBound = false).']) getConstructor()): ?> except(collect($constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['.$this->attributesToStringWithExistingComponentData($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class && ! is_subclass_of($class, DynamicComponent::class)).']); ?>'; +withAttributes(['.$this->attributesToString($attributes->all(), $escapeAttributes = $class !== DynamicComponent::class).']); ?>'; } /** @@ -783,35 +783,6 @@ protected function attributesToString(array $attributes, $escapeBound = true) ->implode(','); } - /** - * Convert an array of attributes to a string using existing component data that has already been evaluated. - * - * @param array $attributes - * @param bool $escapeBound - * @return string - */ - protected function attributesToStringWithExistingComponentData(array $attributes, $escapeBound = true) - { - $results = []; - - foreach ($attributes as $attribute => $value) { - if (! $escapeBound || - ! isset($this->boundAttributes[$attribute]) || - $value === 'true' || - is_numeric($value)) { - $results[] = "'{$attribute}' => {$value}"; - - continue; - } - - $results[] = "'{$attribute}' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(".( - ($attribute === 'attributes') ? $value : ('$componentData[\'data\'][\''.Str::camel($attribute).'\'] ?? null') - ).')'; - } - - return implode(',', $results); - } - /** * Strip any quotes from the given string. * diff --git a/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php b/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php index 5884f3b18dba..88617186fb20 100644 --- a/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php +++ b/src/Illuminate/View/Compilers/Concerns/CompilesComponents.php @@ -67,7 +67,6 @@ public static function compileClassComponentOpening(string $component, string $a { return implode("\n", [ '', - '', '', 'getIterator() : [])); ?>', 'withName('.$alias.'); ?>', @@ -101,10 +100,6 @@ public function compileEndComponentClass() '', '', '', - '', - '', - '', - '', '', '', '', diff --git a/tests/View/Blade/BladeComponentTagCompilerTest.php b/tests/View/Blade/BladeComponentTagCompilerTest.php index 0335f216e96e..5143ae503c72 100644 --- a/tests/View/Blade/BladeComponentTagCompilerTest.php +++ b/tests/View/Blade/BladeComponentTagCompilerTest.php @@ -102,12 +102,12 @@ public function testBasicComponentParsing() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> withAttributes(['type' => 'foo','limit' => '5','@click' => 'foo','wire:click' => 'changePlan(\''.e(\$plan).'\')','required' => true,'x-intersect.margin.-50%.0px' => 'visibleSection = \'profile\'']); ?>\n". -"@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) +"@endComponentClass##END-COMPONENT-CLASS####BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -120,7 +120,7 @@ public function testBasicComponentWithEmptyAttributesParsing() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -133,7 +133,7 @@ public function testDataCamelCasing() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => '1']) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => '1']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -145,7 +145,7 @@ public function testColonData() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => 1]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -157,7 +157,7 @@ public function testColonDataShortSyntax() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => \$userId]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -169,7 +169,7 @@ public function testColonDataWithStaticClassProperty() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => User::\$id]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -181,7 +181,7 @@ public function testColonDataWithStaticClassPropertyAndMultipleAttributes() $this->mockViewFactory(); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', \$componentData = ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'name' => \$name,'value' => 'Joe']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -189,7 +189,7 @@ public function testColonDataWithStaticClassPropertyAndMultipleAttributes() $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', \$componentData = ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['value' => 'Joe','name' => \$name,'label' => Input::\$label]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -201,7 +201,7 @@ public function testSelfClosingComponentWithColonDataShortSyntax() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => \$userId]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => \$userId]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -214,7 +214,7 @@ public function testSelfClosingComponentWithColonDataAndStaticClassPropertyShort $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => User::\$id]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => User::\$id]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -227,7 +227,7 @@ public function testSelfClosingComponentWithColonDataMultipleAttributesAndStatic $this->mockViewFactory(); $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', \$componentData = ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['label' => Input::\$label,'value' => 'Joe','name' => \$name]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -236,7 +236,7 @@ public function testSelfClosingComponentWithColonDataMultipleAttributesAndStatic $result = $this->compiler(['input' => TestInputComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', \$componentData = ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestInputComponent', 'input', ['name' => \$name,'label' => Input::\$label,'value' => 'Joe']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -249,7 +249,7 @@ public function testEscapedColonAttribute() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = ['userId' => 1]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', ['userId' => 1]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -261,11 +261,11 @@ public function testColonAttributesIsEscapedIfStrings() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['src' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['src'] ?? null)]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['src' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('foo')]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testClassDirective() @@ -273,11 +273,11 @@ public function testClassDirective() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['class'] ?? null)]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['class' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssClasses(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testStyleDirective() @@ -285,11 +285,11 @@ public function testStyleDirective() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('true])>'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['style'] ?? null)]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); +withAttributes(['style' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\Illuminate\Support\Arr::toCssStyles(['bar'=>true]))]); ?> @endComponentClass##END-COMPONENT-CLASS##", trim($result)); } public function testColonNestedComponentParsing() @@ -297,7 +297,7 @@ public function testColonNestedComponentParsing() $this->mockViewFactory(); $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -309,7 +309,7 @@ public function testColonStartingNestedComponentParsing() $this->mockViewFactory(); $result = $this->compiler(['foo:alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'foo:alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -321,7 +321,7 @@ public function testSelfClosingComponentsCanBeCompiled() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -363,7 +363,7 @@ public function testComponentsCanBeCompiledWithHyphenAttributes() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -376,7 +376,7 @@ public function testSelfClosingComponentsCanBeCompiledWithDataAndAttributes() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = ['title' => 'foo']) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -390,7 +390,7 @@ public function testComponentCanReceiveAttributeBag() $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -403,7 +403,7 @@ public function testSelfClosingComponentCanReceiveAttributeBag() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('
merge([\'class\' => \'test\']) }} wire:model="foo" />
'); - $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = ['title' => 'foo']) + $this->assertSame("
##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => 'foo']) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -416,7 +416,7 @@ public function testComponentsCanHaveAttachedWord() $this->mockViewFactory(); $result = $this->compiler(['profile' => TestProfileComponent::class])->compileTags('Words'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestProfileComponent', 'profile', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -428,7 +428,7 @@ public function testSelfClosingComponentsCanHaveAttachedWord() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags('Words'); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -441,7 +441,7 @@ public function testSelfClosingComponentsCanBeCompiledWithBoundData() $this->mockViewFactory(); $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = ['title' => \$title]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', ['title' => \$title]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -455,7 +455,7 @@ public function testPairedComponentTags() $result = $this->compiler(['alert' => TestAlertComponent::class])->compileTags(' '); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', \$componentData = []) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\Tests\View\Blade\TestAlertComponent', 'alert', []) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -474,11 +474,11 @@ public function testClasslessComponents() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', \$componentData = ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -493,11 +493,11 @@ public function testClasslessComponentsWithIndexView() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', \$componentData = ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'anonymous-component', ['view' => 'components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -512,11 +512,11 @@ public function testPackagesClasslessComponents() $result = $this->compiler()->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'package::anonymous-component', \$componentData = ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'package::anonymous-component', ['view' => 'package::components.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -546,11 +546,11 @@ public function testClasslessComponentsWithAnonymousComponentNamespace() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'frontend::anonymous-component', \$componentData = ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'frontend::anonymous-component', ['view' => 'public.frontend.anonymous-component','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -580,11 +580,11 @@ public function testClasslessComponentsWithAnonymousComponentNamespaceWithIndexV $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', \$componentData = ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'admin.auth::anonymous-component', ['view' => 'admin.auth.components.anonymous-component.index','data' => ['name' => 'Taylor','age' => 31,'wire:model' => 'foo']]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> -withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute(\$componentData['data']['name'] ?? null),'age' => 31,'wire:model' => 'foo']); ?>\n". +withAttributes(['name' => \Illuminate\View\Compilers\BladeCompiler::sanitizeComponentAttribute('Taylor'),'age' => 31,'wire:model' => 'foo']); ?>\n". '@endComponentClass##END-COMPONENT-CLASS##', trim($result)); } @@ -613,7 +613,7 @@ public function testClasslessComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', \$componentData = ['view' => '".md5('test-directory')."::panel.index','data' => []]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel.index','data' => []]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> @@ -646,7 +646,7 @@ public function testClasslessIndexComponentsWithAnonymousComponentPath() $result = $compiler->compileTags(''); - $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', \$componentData = ['view' => '".md5('test-directory')."::panel','data' => []]) + $this->assertSame("##BEGIN-COMPONENT-CLASS##@component('Illuminate\View\AnonymousComponent', 'panel', ['view' => '".md5('test-directory')."::panel','data' => []]) getConstructor()): ?> except(collect(\$constructor->getParameters())->map->getName()->all()); ?> diff --git a/tests/View/Blade/BladeComponentsTest.php b/tests/View/Blade/BladeComponentsTest.php index 95bc0f8a5825..2615edf91452 100644 --- a/tests/View/Blade/BladeComponentsTest.php +++ b/tests/View/Blade/BladeComponentsTest.php @@ -17,7 +17,6 @@ public function testComponentsAreCompiled() public function testClassComponentsAreCompiled() { $this->assertSame(' - "bar"] + (isset($attributes) && $attributes instanceof Illuminate\View\ComponentAttributeBag ? (array) $attributes->getIterator() : [])); ?> withName(\'test\'); ?> @@ -42,10 +41,6 @@ public function testEndComponentClassesAreCompiled() - - - - From 32a8bb151d748b579c3794dea53b56bd40dfbbd3 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 12 Mar 2024 16:35:43 +0000 Subject: [PATCH 185/325] Update version to v10.48.2 --- 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 ed1c4b83e86e..0b0479bab68f 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.1'; + const VERSION = '10.48.2'; /** * The base path for the Laravel installation. From ce7f5b15305734ddda21b1b2be5b89204d3827e1 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 12 Mar 2024 16:39:30 +0000 Subject: [PATCH 186/325] Update CHANGELOG --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a403b499219f..4a94009a3237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.1...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.2...10.x) + +## [v10.48.2](https://github.com/laravel/framework/compare/v10.48.1...v10.48.2) - 2024-03-12 + +* [10.x] Update mockery conflict to just disallow the broken version by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/50472 +* [10.x] Conflict with specific release by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50473 +* [10.x] Fix for attributes being escaped on Dynamic Blade Components by [@pascalbaljet](https://github.com/pascalbaljet) in https://github.com/laravel/framework/pull/50471 +* [10.x] Revert PR 50403 by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/50482 ## [v10.48.1](https://github.com/laravel/framework/compare/v10.48.0...v10.48.1) - 2024-03-12 From 82441c10287fd3b030b0e2a35c084a1f13d12880 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Wed, 13 Mar 2024 17:30:42 +0100 Subject: [PATCH 187/325] Update actions version --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 53f2553015d2..85b77d6f1a45 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -95,7 +95,7 @@ jobs: AWS_SECRET_ACCESS_KEY: randomSecret - name: Store artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: logs path: | @@ -161,7 +161,7 @@ jobs: AWS_SECRET_ACCESS_KEY: random_secret - name: Store artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: logs path: | From 28f2b1e38cfa2b8f608e8e3e8b11a6c30553978e Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Wed, 13 Mar 2024 17:34:42 +0100 Subject: [PATCH 188/325] wip --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 85b77d6f1a45..3f47b2888780 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,7 +97,7 @@ jobs: - name: Store artifacts uses: actions/upload-artifact@v4 with: - name: logs + name: linux-logs path: | vendor/orchestra/testbench-core/laravel/storage/logs !vendor/**/.gitignore @@ -163,7 +163,7 @@ jobs: - name: Store artifacts uses: actions/upload-artifact@v4 with: - name: logs + name: windows-logs path: | vendor/orchestra/testbench-core/laravel/storage/logs !vendor/**/.gitignore From 2d844fe16da5018d2358a3275be6dbf04346915a Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Wed, 13 Mar 2024 17:39:35 +0100 Subject: [PATCH 189/325] wip --- .github/workflows/tests.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3f47b2888780..1a4feecb7ce0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -97,7 +97,7 @@ jobs: - name: Store artifacts uses: actions/upload-artifact@v4 with: - name: linux-logs + name: linux-logs-${{ matrix.php }}-${{ matrix.stability }} path: | vendor/orchestra/testbench-core/laravel/storage/logs !vendor/**/.gitignore @@ -163,7 +163,7 @@ jobs: - name: Store artifacts uses: actions/upload-artifact@v4 with: - name: windows-logs + name: windows-logs-${{ matrix.php }}-${{ matrix.stability }} path: | vendor/orchestra/testbench-core/laravel/storage/logs !vendor/**/.gitignore From 5791c052b41c6b593556adc687076bfbdd13c501 Mon Sep 17 00:00:00 2001 From: driesvints Date: Fri, 15 Mar 2024 10:17:07 +0000 Subject: [PATCH 190/325] Update version to v10.48.3 --- 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 0b0479bab68f..a35b35c10664 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.2'; + const VERSION = '10.48.3'; /** * The base path for the Laravel installation. From fc7b8f6984c58265d1fe111dafa124f9620db767 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 15 Mar 2024 18:39:46 +0100 Subject: [PATCH 191/325] Update CHANGELOG.md --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a94009a3237..b9c0d153a75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.2...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.3...10.x) + +## [v10.48.3](https://github.com/laravel/framework/compare/v10.48.2...v10.48.3) - 2024-03-15 + +- Re-tag version ## [v10.48.2](https://github.com/laravel/framework/compare/v10.48.1...v10.48.2) - 2024-03-12 From 8fc7bd47d62fcfa48eb3369f040addabd39dc946 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 20 Mar 2024 04:04:10 +0800 Subject: [PATCH 192/325] [10.x] Test Improvements (#50642) * Test Improvements Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update composer.json --------- Signed-off-by: Mior Muhammad Zaki --- composer.json | 2 +- tests/Support/SupportCollectionTest.php | 4 ++++ tests/Support/SupportNumberTest.php | 32 +++++++------------------ 3 files changed, 14 insertions(+), 24 deletions(-) diff --git a/composer.json b/composer.json index f4abfd3b3f8d..169bc9942622 100644 --- a/composer.json +++ b/composer.json @@ -105,7 +105,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.5.1", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^8.18", + "orchestra/testbench-core": "^8.23.4", "pda/pheanstalk": "^4.0", "phpstan/phpstan": "^1.4.7", "phpunit/phpunit": "^10.0.7", diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 7c8d47a0d4ea..6eb4f372bf44 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -2095,6 +2095,8 @@ public function testSortByAlwaysReturnsAssoc($collection) #[DataProvider('collectionClassProvider')] public function testSortByMany($collection) { + $defaultLocale = setlocale(LC_ALL, 0); + $data = new $collection([['item' => '1'], ['item' => '10'], ['item' => 5], ['item' => 20]]); $expected = $data->pluck('item')->toArray(); @@ -2172,6 +2174,8 @@ public function testSortByMany($collection) sort($expected, SORT_LOCALE_STRING); $data = $data->sortBy(['item'], SORT_LOCALE_STRING); $this->assertEquals($data->pluck('item')->toArray(), $expected); + + setlocale(LC_ALL, $defaultLocale); } /** diff --git a/tests/Support/SupportNumberTest.php b/tests/Support/SupportNumberTest.php index c15dafddf812..e54c03d77d1c 100644 --- a/tests/Support/SupportNumberTest.php +++ b/tests/Support/SupportNumberTest.php @@ -3,14 +3,14 @@ namespace Illuminate\Tests\Support; use Illuminate\Support\Number; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use PHPUnit\Framework\TestCase; class SupportNumberTest extends TestCase { + #[RequiresPhpExtension('intl')] public function testFormat() { - $this->needsIntlExtension(); - $this->assertSame('0', Number::format(0)); $this->assertSame('0', Number::format(0.0)); $this->assertSame('0', Number::format(0.00)); @@ -40,10 +40,9 @@ public function testFormat() $this->assertSame('NaN', Number::format(NAN)); } + #[RequiresPhpExtension('intl')] public function testFormatWithDifferentLocale() { - $this->needsIntlExtension(); - $this->assertSame('123,456,789', Number::format(123456789, locale: 'en')); $this->assertSame('123.456.789', Number::format(123456789, locale: 'de')); $this->assertSame('123 456 789', Number::format(123456789, locale: 'fr')); @@ -51,10 +50,9 @@ public function testFormatWithDifferentLocale() $this->assertSame('123 456 789', Number::format(123456789, locale: 'sv')); } + #[RequiresPhpExtension('intl')] public function testFormatWithAppLocale() { - $this->needsIntlExtension(); - $this->assertSame('123,456,789', Number::format(123456789)); Number::useLocale('de'); @@ -70,17 +68,15 @@ public function testSpellout() $this->assertSame('one point two', Number::spell(1.2)); } + #[RequiresPhpExtension('intl')] public function testSpelloutWithLocale() { - $this->needsIntlExtension(); - $this->assertSame('trois', Number::spell(3, 'fr')); } + #[RequiresPhpExtension('intl')] public function testSpelloutWithThreshold() { - $this->needsIntlExtension(); - $this->assertSame('9', Number::spell(9, after: 10)); $this->assertSame('10', Number::spell(10, after: 10)); $this->assertSame('eleven', Number::spell(11, after: 10)); @@ -100,10 +96,9 @@ public function testOrdinal() $this->assertSame('3rd', Number::ordinal(3)); } + #[RequiresPhpExtension('intl')] public function testToPercent() { - $this->needsIntlExtension(); - $this->assertSame('0%', Number::percentage(0, precision: 0)); $this->assertSame('0%', Number::percentage(0)); $this->assertSame('1%', Number::percentage(1)); @@ -124,10 +119,9 @@ public function testToPercent() $this->assertSame('0.1235%', Number::percentage(0.12345, precision: 4)); } + #[RequiresPhpExtension('intl')] public function testToCurrency() { - $this->needsIntlExtension(); - $this->assertSame('$0.00', Number::currency(0)); $this->assertSame('$1.00', Number::currency(1)); $this->assertSame('$10.00', Number::currency(10)); @@ -141,10 +135,9 @@ public function testToCurrency() $this->assertSame('$5.32', Number::currency(5.325)); } + #[RequiresPhpExtension('intl')] public function testToCurrencyWithDifferentLocale() { - $this->needsIntlExtension(); - $this->assertSame('1,00 €', Number::currency(1, 'EUR', 'de')); $this->assertSame('1,00 $', Number::currency(1, 'USD', 'de')); $this->assertSame('1,00 £', Number::currency(1, 'GBP', 'de')); @@ -293,11 +286,4 @@ public function testSummarize() $this->assertSame('-1Q', Number::abbreviate(-1000000000000000)); $this->assertSame('-1KQ', Number::abbreviate(-1000000000000000000)); } - - protected function needsIntlExtension() - { - if (! extension_loaded('intl')) { - $this->markTestSkipped('The intl extension is not installed. Please install the extension to enable '.__CLASS__); - } - } } From 9f26b84c0cd0b3fb1dd7924eaa490e20fe9675c2 Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Wed, 20 Mar 2024 21:09:13 +0100 Subject: [PATCH 193/325] Fix `Collection::concat()` return type (#50669) --- src/Illuminate/Collections/Collection.php | 7 +++++-- src/Illuminate/Collections/Enumerable.php | 7 +++++-- src/Illuminate/Collections/LazyCollection.php | 7 +++++-- types/Support/Collection.php | 1 + types/Support/LazyCollection.php | 1 + 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 0bbbe189816f..c46fc052f972 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -998,8 +998,11 @@ public function push(...$values) /** * Push all of the given items onto the collection. * - * @param iterable $source - * @return static + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static */ public function concat($source) { diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index a561488e8a59..918f64758e66 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -789,8 +789,11 @@ public function partition($key, $operator = null, $value = null); /** * Push all of the given items onto the collection. * - * @param iterable $source - * @return static + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static */ public function concat($source); diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index a8b7716d35d6..84b22ebf9257 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -991,8 +991,11 @@ public function select($keys) /** * Push all of the given items onto the collection. * - * @param iterable $source - * @return static + * @template TConcatKey of array-key + * @template TConcatValue + * + * @param iterable $source + * @return static */ public function concat($source) { diff --git a/types/Support/Collection.php b/types/Support/Collection.php index 77861041deaf..2e8b4a926386 100644 --- a/types/Support/Collection.php +++ b/types/Support/Collection.php @@ -657,6 +657,7 @@ function ($collection, $count) { assertType('Illuminate\Support\Collection', $collection->make([1])->concat([2])); assertType('Illuminate\Support\Collection', $collection->make(['string'])->concat(['string'])); +assertType('Illuminate\Support\Collection', $collection->make([1])->concat(['string'])); assertType('Illuminate\Support\Collection|int', $collection->make([1])->random(2)); assertType('Illuminate\Support\Collection|string', $collection->make(['string'])->random()); diff --git a/types/Support/LazyCollection.php b/types/Support/LazyCollection.php index c9ad444ec154..ef3c5f21578a 100644 --- a/types/Support/LazyCollection.php +++ b/types/Support/LazyCollection.php @@ -547,6 +547,7 @@ assertType('Illuminate\Support\LazyCollection', $collection->make([1])->concat([2])); assertType('Illuminate\Support\LazyCollection', $collection->make(['string'])->concat(['string'])); +assertType('Illuminate\Support\LazyCollection', $collection->make([1])->concat(['string'])); assertType('Illuminate\Support\LazyCollection|int', $collection->make([1])->random(2)); assertType('Illuminate\Support\LazyCollection|string', $collection->make(['string'])->random()); From 3d3063c5d03d6ef69e4a31bac689545127260a69 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 21 Mar 2024 16:44:47 +0800 Subject: [PATCH 194/325] [10.x] Test Improvements for Mail & Notifications tests (#50690) * [10.x] Test Improvements for Mail & Notifications tests Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- .../Mail/RenderingMailWithLocaleTest.php | 7 +++---- .../Mail/SendingMailWithLocaleTest.php | 7 +++---- .../Mail/SendingMarkdownMailTest.php | 5 ++--- .../Integration/Mail/SendingQueuedMailTest.php | 3 +-- tests/Integration/Mail/SentMessageMailTest.php | 12 +++++++++--- .../SendingMailNotificationsTest.php | 12 ++---------- .../SendingMailableNotificationsTest.php | 17 ++++++++++------- ...gNotificationsViaAnonymousNotifiableTest.php | 2 -- .../SendingNotificationsWithLocaleTest.php | 9 +++------ 9 files changed, 33 insertions(+), 41 deletions(-) diff --git a/tests/Integration/Mail/RenderingMailWithLocaleTest.php b/tests/Integration/Mail/RenderingMailWithLocaleTest.php index 2780d60b5568..b3cd5e995657 100644 --- a/tests/Integration/Mail/RenderingMailWithLocaleTest.php +++ b/tests/Integration/Mail/RenderingMailWithLocaleTest.php @@ -3,18 +3,17 @@ namespace Illuminate\Tests\Integration\Mail; use Illuminate\Mail\Mailable; -use Illuminate\Support\Facades\View; use Orchestra\Testbench\TestCase; class RenderingMailWithLocaleTest extends TestCase { - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { $app['config']->set('app.locale', 'en'); - View::addLocation(__DIR__.'/Fixtures'); + $app['view']->addLocation(__DIR__.'/Fixtures'); - app('translator')->setLoaded([ + $app['translator']->setLoaded([ '*' => [ '*' => [ 'en' => ['nom' => 'name'], diff --git a/tests/Integration/Mail/SendingMailWithLocaleTest.php b/tests/Integration/Mail/SendingMailWithLocaleTest.php index 8b307c058188..06a6dc096e45 100644 --- a/tests/Integration/Mail/SendingMailWithLocaleTest.php +++ b/tests/Integration/Mail/SendingMailWithLocaleTest.php @@ -9,21 +9,20 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Mail; -use Illuminate\Support\Facades\View; use Illuminate\Testing\Assert; use Orchestra\Testbench\TestCase; class SendingMailWithLocaleTest extends TestCase { - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { $app['config']->set('mail.driver', 'array'); $app['config']->set('app.locale', 'en'); - View::addLocation(__DIR__.'/Fixtures'); + $app['view']->addLocation(__DIR__.'/Fixtures'); - app('translator')->setLoaded([ + $app['translator']->setLoaded([ '*' => [ '*' => [ 'en' => ['nom' => 'name'], diff --git a/tests/Integration/Mail/SendingMarkdownMailTest.php b/tests/Integration/Mail/SendingMarkdownMailTest.php index b3e5a561b85d..69bb8bb4d529 100644 --- a/tests/Integration/Mail/SendingMarkdownMailTest.php +++ b/tests/Integration/Mail/SendingMarkdownMailTest.php @@ -7,7 +7,6 @@ use Illuminate\Mail\Mailables\Envelope; use Illuminate\Mail\Markdown; use Illuminate\Support\Facades\Mail; -use Illuminate\Support\Facades\View; use Orchestra\Testbench\TestCase; class SendingMarkdownMailTest extends TestCase @@ -16,8 +15,8 @@ protected function getEnvironmentSetUp($app) { $app['config']->set('mail.driver', 'array'); - View::addNamespace('mail', __DIR__.'/Fixtures'); - View::addLocation(__DIR__.'/Fixtures'); + $app['view']->addNamespace('mail', __DIR__.'/Fixtures') + ->addLocation(__DIR__.'/Fixtures'); } public function testMailIsSent() diff --git a/tests/Integration/Mail/SendingQueuedMailTest.php b/tests/Integration/Mail/SendingQueuedMailTest.php index 6f5cc503ee95..6f982043ce65 100644 --- a/tests/Integration/Mail/SendingQueuedMailTest.php +++ b/tests/Integration/Mail/SendingQueuedMailTest.php @@ -7,7 +7,6 @@ use Illuminate\Queue\Middleware\RateLimited; use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Queue; -use Illuminate\Support\Facades\View; use Orchestra\Testbench\TestCase; class SendingQueuedMailTest extends TestCase @@ -16,7 +15,7 @@ protected function getEnvironmentSetUp($app) { $app['config']->set('mail.driver', 'array'); - View::addLocation(__DIR__.'/Fixtures'); + $app['view']->addLocation(__DIR__.'/Fixtures'); } public function testMailIsSentWithDefaultLocale() diff --git a/tests/Integration/Mail/SentMessageMailTest.php b/tests/Integration/Mail/SentMessageMailTest.php index af72a6c33159..76350090e3fd 100644 --- a/tests/Integration/Mail/SentMessageMailTest.php +++ b/tests/Integration/Mail/SentMessageMailTest.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Notifications\Events\NotificationSent; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notifiable; @@ -14,15 +15,20 @@ class SentMessageMailTest extends TestCase { - protected function setUp(): void - { - parent::setUp(); + use LazilyRefreshDatabase; + protected function afterRefreshingDatabase() + { Schema::create('sent_message_users', function (Blueprint $table) { $table->increments('id'); }); } + protected function beforeRefreshingDatabase() + { + Schema::dropIfExists('sent_message_users'); + } + public function testDispatchesNotificationSent() { $notificationWasSent = false; diff --git a/tests/Integration/Notifications/SendingMailNotificationsTest.php b/tests/Integration/Notifications/SendingMailNotificationsTest.php index 26cf3e4e391b..9b59f4c7c0da 100644 --- a/tests/Integration/Notifications/SendingMailNotificationsTest.php +++ b/tests/Integration/Notifications/SendingMailNotificationsTest.php @@ -14,7 +14,6 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Schema; -use Illuminate\Support\Facades\View; use Illuminate\Support\Str; use Mockery as m; use Orchestra\Testbench\TestCase; @@ -25,14 +24,7 @@ class SendingMailNotificationsTest extends TestCase public $mailer; public $markdown; - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { $this->mailFactory = m::mock(MailFactory::class); $this->mailer = m::mock(Mailer::class); @@ -51,7 +43,7 @@ protected function getEnvironmentSetUp($app) return $this->mailFactory; }); - View::addLocation(__DIR__.'/Fixtures'); + $app['view']->addLocation(__DIR__.'/Fixtures'); } protected function setUp(): void diff --git a/tests/Integration/Notifications/SendingMailableNotificationsTest.php b/tests/Integration/Notifications/SendingMailableNotificationsTest.php index c074e4490974..dcbecf0c4fb1 100644 --- a/tests/Integration/Notifications/SendingMailableNotificationsTest.php +++ b/tests/Integration/Notifications/SendingMailableNotificationsTest.php @@ -4,18 +4,18 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Notifiable; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Schema; -use Illuminate\Support\Facades\View; use Orchestra\Testbench\TestCase; class SendingMailableNotificationsTest extends TestCase { - public $mailer; + use RefreshDatabase; - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { $app['config']->set('mail.driver', 'array'); @@ -23,13 +23,11 @@ protected function getEnvironmentSetUp($app) $app['config']->set('mail.markdown.theme', 'blank'); - View::addLocation(__DIR__.'/Fixtures'); + $app['view']->addLocation(__DIR__.'/Fixtures'); } - protected function setUp(): void + protected function afterRefreshingDatabase() { - parent::setUp(); - Schema::create('users', function (Blueprint $table) { $table->increments('id'); $table->string('email'); @@ -37,6 +35,11 @@ protected function setUp(): void }); } + protected function beforeRefreshingDatabase() + { + Schema::dropIfExists('users'); + } + public function testMarkdownNotification() { $user = MailableNotificationUser::forceCreate([ diff --git a/tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php b/tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php index 38f7f252bdbf..4ba3ea787a54 100644 --- a/tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php +++ b/tests/Integration/Notifications/SendingNotificationsViaAnonymousNotifiableTest.php @@ -10,8 +10,6 @@ class SendingNotificationsViaAnonymousNotifiableTest extends TestCase { - public $mailer; - public function testMailIsSent() { $notifiable = (new AnonymousNotifiable) diff --git a/tests/Integration/Notifications/SendingNotificationsWithLocaleTest.php b/tests/Integration/Notifications/SendingNotificationsWithLocaleTest.php index 9b9862abfa51..95a0f4e39f38 100644 --- a/tests/Integration/Notifications/SendingNotificationsWithLocaleTest.php +++ b/tests/Integration/Notifications/SendingNotificationsWithLocaleTest.php @@ -15,23 +15,20 @@ use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Notification as NotificationFacade; use Illuminate\Support\Facades\Schema; -use Illuminate\Support\Facades\View; use Illuminate\Testing\Assert; use Orchestra\Testbench\TestCase; class SendingNotificationsWithLocaleTest extends TestCase { - public $mailer; - - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { $app['config']->set('mail.driver', 'array'); $app['config']->set('app.locale', 'en'); - View::addLocation(__DIR__.'/Fixtures'); + $app['view']->addLocation(__DIR__.'/Fixtures'); - app('translator')->setLoaded([ + $app['translator']->setLoaded([ '*' => [ '*' => [ 'en' => ['hi' => 'hello'], From ebf2a8343384c8b8f7799f6d7f3b8be2d06011d6 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 21 Mar 2024 16:45:06 +0800 Subject: [PATCH 195/325] [10.x] Test Improvements for HTTP tests (#50687) Signed-off-by: Mior Muhammad Zaki --- .../Http/Middleware/HandleCorsTest.php | 26 +++++++------------ .../Integration/Http/ThrottleRequestsTest.php | 14 ++-------- .../Http/ThrottleRequestsWithRedisTest.php | 13 ++-------- 3 files changed, 14 insertions(+), 39 deletions(-) diff --git a/tests/Integration/Http/Middleware/HandleCorsTest.php b/tests/Integration/Http/Middleware/HandleCorsTest.php index e30eaf8324f3..38ed647a621a 100644 --- a/tests/Integration/Http/Middleware/HandleCorsTest.php +++ b/tests/Integration/Http/Middleware/HandleCorsTest.php @@ -13,23 +13,8 @@ class HandleCorsTest extends TestCase { use ValidatesRequests; - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { - $kernel = $app->make(Kernel::class); - $kernel->prependMiddleware(HandleCors::class); - - $router = $app['router']; - - $this->addWebRoutes($router); - $this->addApiRoutes($router); - - parent::getEnvironmentSetUp($app); - } - - protected function resolveApplicationConfiguration($app) - { - parent::resolveApplicationConfiguration($app); - $app['config']['cors'] = [ 'paths' => ['api/*'], 'supports_credentials' => false, @@ -39,6 +24,15 @@ protected function resolveApplicationConfiguration($app) 'exposed_headers' => [], 'max_age' => 0, ]; + + $kernel = $app->make(Kernel::class); + $kernel->prependMiddleware(HandleCors::class); + } + + protected function defineRoutes($router) + { + $this->addWebRoutes($router); + $this->addApiRoutes($router); } public function testShouldReturnHeaderAssessControlAllowOriginWhenDontHaveHttpOriginOnRequest() diff --git a/tests/Integration/Http/ThrottleRequestsTest.php b/tests/Integration/Http/ThrottleRequestsTest.php index 6ddd2a0ef51e..d3cb97cd3d20 100644 --- a/tests/Integration/Http/ThrottleRequestsTest.php +++ b/tests/Integration/Http/ThrottleRequestsTest.php @@ -9,23 +9,13 @@ use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Route; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; use Throwable; +#[WithConfig('hashing.driver', 'bcrypt')] class ThrottleRequestsTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - Carbon::setTestNow(null); - } - - public function getEnvironmentSetUp($app) - { - $app['config']->set('hashing', ['driver' => 'bcrypt']); - } - public function testLockOpensImmediatelyAfterDecay() { Carbon::setTestNow(Carbon::create(2018, 1, 1, 0, 0, 0)); diff --git a/tests/Integration/Http/ThrottleRequestsWithRedisTest.php b/tests/Integration/Http/ThrottleRequestsWithRedisTest.php index ef7a98b75401..37f98dc73f33 100644 --- a/tests/Integration/Http/ThrottleRequestsWithRedisTest.php +++ b/tests/Integration/Http/ThrottleRequestsWithRedisTest.php @@ -6,24 +6,15 @@ use Illuminate\Routing\Middleware\ThrottleRequestsWithRedis; use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Route; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; use Throwable; +#[WithConfig('hashing.driver', 'bcrypt')] class ThrottleRequestsWithRedisTest extends TestCase { use InteractsWithRedis; - protected function tearDown(): void - { - parent::tearDown(); - Carbon::setTestNow(null); - } - - public function getEnvironmentSetUp($app) - { - $app['config']->set('hashing', ['driver' => 'bcrypt']); - } - public function testLockOpensImmediatelyAfterDecay() { $this->ifRedisAvailable(function () { From f2ec6fa15151844748ef920f57816ff7b41369f0 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 21 Mar 2024 16:45:26 +0800 Subject: [PATCH 196/325] [10.x] Test Improvements for Console tests (#50685) Signed-off-by: Mior Muhammad Zaki --- tests/Integration/Console/CommandEventsTest.php | 5 ++--- tests/Integration/Console/CommandSchedulingTest.php | 5 ++--- tests/Integration/Console/GeneratorCommandTest.php | 5 ++--- tests/Integration/Console/Scheduling/CallbackEventTest.php | 7 ------- tests/Integration/Console/Scheduling/EventPingTest.php | 7 ------- .../Console/Scheduling/SubMinuteSchedulingTest.php | 3 ++- tests/Integration/Cookie/CookieTest.php | 7 ------- 7 files changed, 8 insertions(+), 31 deletions(-) diff --git a/tests/Integration/Console/CommandEventsTest.php b/tests/Integration/Console/CommandEventsTest.php index 167835eb9b7f..cc63e22dc4fc 100644 --- a/tests/Integration/Console/CommandEventsTest.php +++ b/tests/Integration/Console/CommandEventsTest.php @@ -12,6 +12,7 @@ use Illuminate\Support\Str; use Orchestra\Testbench\Foundation\Application as Testbench; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class CommandEventsTest extends TestCase { @@ -48,9 +49,7 @@ protected function setUp(): void parent::setUp(); } - /** - * @dataProvider foregroundCommandEventsProvider - */ + #[DataProvider('foregroundCommandEventsProvider')] public function testCommandEventsReceiveParsedInput($callback) { $this->app[ConsoleKernel::class]->registerCommand(new CommandEventsTestCommand); diff --git a/tests/Integration/Console/CommandSchedulingTest.php b/tests/Integration/Console/CommandSchedulingTest.php index 1e02c3b3d2fa..1a4c918aa5e0 100644 --- a/tests/Integration/Console/CommandSchedulingTest.php +++ b/tests/Integration/Console/CommandSchedulingTest.php @@ -6,6 +6,7 @@ use Illuminate\Filesystem\Filesystem; use Illuminate\Support\Str; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class CommandSchedulingTest extends TestCase { @@ -62,9 +63,7 @@ protected function tearDown(): void parent::tearDown(); } - /** - * @dataProvider executionProvider - */ + #[DataProvider('executionProvider')] public function testExecutionOrder($background, $expected) { $schedule = $this->app->make(Schedule::class); diff --git a/tests/Integration/Console/GeneratorCommandTest.php b/tests/Integration/Console/GeneratorCommandTest.php index 18889bf5ed3b..cb8ce75a6818 100644 --- a/tests/Integration/Console/GeneratorCommandTest.php +++ b/tests/Integration/Console/GeneratorCommandTest.php @@ -3,12 +3,11 @@ namespace Illuminate\Tests\Integration\Console; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class GeneratorCommandTest extends TestCase { - /** - * @dataProvider reservedNamesDataProvider - */ + #[DataProvider('reservedNamesDataProvider')] public function testItCannotGenerateClassUsingReservedName($given) { $this->artisan('make:command', ['name' => $given]) diff --git a/tests/Integration/Console/Scheduling/CallbackEventTest.php b/tests/Integration/Console/Scheduling/CallbackEventTest.php index f884a6f3b80b..586857bd4151 100644 --- a/tests/Integration/Console/Scheduling/CallbackEventTest.php +++ b/tests/Integration/Console/Scheduling/CallbackEventTest.php @@ -10,13 +10,6 @@ class CallbackEventTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testDefaultResultIsSuccess() { $success = null; diff --git a/tests/Integration/Console/Scheduling/EventPingTest.php b/tests/Integration/Console/Scheduling/EventPingTest.php index 04c4774d3fc2..f5b648cac661 100644 --- a/tests/Integration/Console/Scheduling/EventPingTest.php +++ b/tests/Integration/Console/Scheduling/EventPingTest.php @@ -16,13 +16,6 @@ class EventPingTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - m::close(); - } - public function testPingRescuesTransferExceptions() { $this->spy(ExceptionHandler::class) diff --git a/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php b/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php index 05f220576f79..de6f9cb3ae1e 100644 --- a/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php +++ b/tests/Integration/Console/Scheduling/SubMinuteSchedulingTest.php @@ -7,6 +7,7 @@ use Illuminate\Support\Facades\Config; use Illuminate\Support\Sleep; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class SubMinuteSchedulingTest extends TestCase { @@ -45,7 +46,7 @@ public function test_it_doesnt_wait_for_sub_minute_events_when_none_are_schedule Sleep::assertNeverSlept(); } - /** @dataProvider frequencyProvider */ + #[DataProvider('frequencyProvider')] public function test_it_runs_sub_minute_callbacks($frequency, $expectedRuns) { $runs = 0; diff --git a/tests/Integration/Cookie/CookieTest.php b/tests/Integration/Cookie/CookieTest.php index 83aa4ab9b9a4..bc5252630376 100644 --- a/tests/Integration/Cookie/CookieTest.php +++ b/tests/Integration/Cookie/CookieTest.php @@ -14,13 +14,6 @@ class CookieTest extends TestCase { - protected function tearDown(): void - { - parent::tearDown(); - - Carbon::setTestNow(null); - } - public function test_cookie_is_sent_back_with_proper_expire_time_when_should_expire_on_close() { $this->app['config']->set('session.expire_on_close', true); From 8a1a5a20c86b20795386e2669c08d0cc3acd1871 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 21 Mar 2024 16:45:53 +0800 Subject: [PATCH 197/325] [10.x] Test Improvements for Cache tests (#50684) * [10.x] Test Improvements for Cache tests Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update MemcachedTaggedCacheTestCase.php * Update PhpRedisCacheLockTest.php * wip --------- Signed-off-by: Mior Muhammad Zaki --- tests/Integration/Cache/DynamoDbStoreTest.php | 11 ++-------- tests/Integration/Cache/FileCacheLockTest.php | 13 ++---------- .../Cache/MemcachedCacheLockTestCase.php | 5 ++--- .../Cache/MemcachedTaggedCacheTestCase.php | 5 ++--- tests/Integration/Cache/NoLockTest.php | 20 +++---------------- .../Cache/PhpRedisCacheLockTest.php | 17 +++++----------- tests/Integration/Cache/Psr6RedisTest.php | 5 ++--- 7 files changed, 18 insertions(+), 58 deletions(-) diff --git a/tests/Integration/Cache/DynamoDbStoreTest.php b/tests/Integration/Cache/DynamoDbStoreTest.php index e59ebbfd774b..b465cc61ea0e 100644 --- a/tests/Integration/Cache/DynamoDbStoreTest.php +++ b/tests/Integration/Cache/DynamoDbStoreTest.php @@ -7,19 +7,12 @@ use Illuminate\Contracts\Cache\Repository; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; +use Orchestra\Testbench\Attributes\RequiresEnv; use Orchestra\Testbench\TestCase; +#[RequiresEnv('DYNAMODB_CACHE_TABLE')] class DynamoDbStoreTest extends TestCase { - protected function setUp(): void - { - if (! env('DYNAMODB_CACHE_TABLE')) { - $this->markTestSkipped('DynamoDB not configured.'); - } - - parent::setUp(); - } - public function testItemsCanBeStoredAndRetrieved() { Cache::driver('dynamodb')->put('name', 'Taylor', 10); diff --git a/tests/Integration/Cache/FileCacheLockTest.php b/tests/Integration/Cache/FileCacheLockTest.php index c00409fe9e0e..594471806393 100644 --- a/tests/Integration/Cache/FileCacheLockTest.php +++ b/tests/Integration/Cache/FileCacheLockTest.php @@ -4,21 +4,12 @@ use Exception; use Illuminate\Support\Facades\Cache; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; +#[WithConfig('cache.default', 'file')] class FileCacheLockTest extends TestCase { - /** - * Define environment setup. - * - * @param \Illuminate\Foundation\Application $app - * @return void - */ - protected function getEnvironmentSetUp($app) - { - $app['config']->set('cache.default', 'file'); - } - public function testLocksCanBeAcquiredAndReleased() { Cache::lock('foo')->forceRelease(); diff --git a/tests/Integration/Cache/MemcachedCacheLockTestCase.php b/tests/Integration/Cache/MemcachedCacheLockTestCase.php index 8dbbd481f17e..e4fc82dca804 100644 --- a/tests/Integration/Cache/MemcachedCacheLockTestCase.php +++ b/tests/Integration/Cache/MemcachedCacheLockTestCase.php @@ -4,10 +4,9 @@ use Illuminate\Contracts\Cache\LockTimeoutException; use Illuminate\Support\Facades\Cache; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension memcached - */ +#[RequiresPhpExtension('memcached')] class MemcachedCacheLockTestCase extends MemcachedIntegrationTestCase { public function testMemcachedLocksCanBeAcquiredAndReleased() diff --git a/tests/Integration/Cache/MemcachedTaggedCacheTestCase.php b/tests/Integration/Cache/MemcachedTaggedCacheTestCase.php index 4aab9422a8fd..ee7787aa407e 100644 --- a/tests/Integration/Cache/MemcachedTaggedCacheTestCase.php +++ b/tests/Integration/Cache/MemcachedTaggedCacheTestCase.php @@ -3,10 +3,9 @@ namespace Illuminate\Tests\Integration\Cache; use Illuminate\Support\Facades\Cache; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension memcached - */ +#[RequiresPhpExtension('memcached')] class MemcachedTaggedCacheTestCase extends MemcachedIntegrationTestCase { public function testMemcachedCanStoreAndRetrieveTaggedCacheItems() diff --git a/tests/Integration/Cache/NoLockTest.php b/tests/Integration/Cache/NoLockTest.php index 9f1fc983b3aa..3851ea1278c2 100644 --- a/tests/Integration/Cache/NoLockTest.php +++ b/tests/Integration/Cache/NoLockTest.php @@ -3,27 +3,13 @@ namespace Illuminate\Tests\Integration\Cache; use Illuminate\Support\Facades\Cache; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; +#[WithConfig('cache.default', 'null')] +#[WithConfig('cache.stores.null', ['driver' => 'null'])] class NoLockTest extends TestCase { - /** - * Define environment setup. - * - * @param \Illuminate\Foundation\Application $app - * @return void - */ - protected function getEnvironmentSetUp($app) - { - $app['config']->set('cache.default', 'null'); - - $app['config']->set('cache.stores', [ - 'null' => [ - 'driver' => 'null', - ], - ]); - } - public function testLocksCanAlwaysBeAcquiredAndReleased() { Cache::lock('foo')->forceRelease(); diff --git a/tests/Integration/Cache/PhpRedisCacheLockTest.php b/tests/Integration/Cache/PhpRedisCacheLockTest.php index 0ca4ea85f960..63016545fdcf 100644 --- a/tests/Integration/Cache/PhpRedisCacheLockTest.php +++ b/tests/Integration/Cache/PhpRedisCacheLockTest.php @@ -5,6 +5,7 @@ use Illuminate\Foundation\Testing\Concerns\InteractsWithRedis; use Illuminate\Support\Facades\Cache; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; use Redis; class PhpRedisCacheLockTest extends TestCase @@ -138,9 +139,7 @@ public function testRedisLockCanBeAcquiredAndReleasedWithMsgpackSerialization() $this->assertNull($store->lockConnection()->get($store->getPrefix().'foo')); } - /** - * @requires extension lzf - */ + #[RequiresPhpExtension('lzf')] public function testRedisLockCanBeAcquiredAndReleasedWithLzfCompression() { if (! defined('Redis::COMPRESSION_LZF')) { @@ -167,9 +166,7 @@ public function testRedisLockCanBeAcquiredAndReleasedWithLzfCompression() $this->assertNull($store->lockConnection()->get($store->getPrefix().'foo')); } - /** - * @requires extension zstd - */ + #[RequiresPhpExtension('zstd')] public function testRedisLockCanBeAcquiredAndReleasedWithZstdCompression() { if (! defined('Redis::COMPRESSION_ZSTD')) { @@ -215,9 +212,7 @@ public function testRedisLockCanBeAcquiredAndReleasedWithZstdCompression() $this->assertNull($store->lockConnection()->get($store->getPrefix().'foo')); } - /** - * @requires extension lz4 - */ + #[RequiresPhpExtension('lz4')] public function testRedisLockCanBeAcquiredAndReleasedWithLz4Compression() { if (! defined('Redis::COMPRESSION_LZ4')) { @@ -263,9 +258,7 @@ public function testRedisLockCanBeAcquiredAndReleasedWithLz4Compression() $this->assertNull($store->lockConnection()->get($store->getPrefix().'foo')); } - /** - * @requires extension Lzf - */ + #[RequiresPhpExtension('Lzf')] public function testRedisLockCanBeAcquiredAndReleasedWithSerializationAndCompression() { if (! defined('Redis::COMPRESSION_LZF')) { diff --git a/tests/Integration/Cache/Psr6RedisTest.php b/tests/Integration/Cache/Psr6RedisTest.php index a242a2f77e29..062d5ec4dbfb 100644 --- a/tests/Integration/Cache/Psr6RedisTest.php +++ b/tests/Integration/Cache/Psr6RedisTest.php @@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Cache; use Illuminate\Tests\Integration\Cache\Fixtures\Unserializable; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class Psr6RedisTest extends TestCase { @@ -25,9 +26,7 @@ protected function tearDown(): void $this->tearDownRedis(); } - /** - * @dataProvider redisClientDataProvider - */ + #[DataProvider('redisClientDataProvider')] public function testTransactionIsNotOpenedWhenSerializationFails($redisClient): void { $this->app['config']['cache.default'] = 'redis'; From 1ad93540156a5049c0fabc975063efd08673baa1 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 21 Mar 2024 16:46:48 +0800 Subject: [PATCH 198/325] [10.x] Test Improvements for Auth tests (#50683) * Test Improvements for Auth tests Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- .../ApiAuthenticationWithEloquentTest.php | 14 ++---- tests/Integration/Auth/AuthenticationTest.php | 50 +++++++++++-------- tests/Integration/Auth/ForgotPasswordTest.php | 10 ++-- ...ForgotPasswordWithoutDefaultRoutesTest.php | 10 ++-- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php b/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php index c5fc77aec88d..ca09a8c89213 100644 --- a/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php +++ b/tests/Integration/Auth/ApiAuthenticationWithEloquentTest.php @@ -3,21 +3,18 @@ namespace Illuminate\Tests\Integration\Auth; use Illuminate\Database\QueryException; -use Illuminate\Foundation\Auth\User as FoundationUser; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\RequiresPhpExtension; -/** - * @requires extension pdo_mysql - */ +#[RequiresPhpExtension('pdo_mysql')] class ApiAuthenticationWithEloquentTest extends TestCase { - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { // Auth configuration $app['config']->set('auth.defaults.guard', 'api'); - $app['config']->set('auth.providers.users.model', User::class); $app['config']->set('auth.guards.api', [ 'driver' => 'token', @@ -59,8 +56,3 @@ public function testAuthenticationViaApiWithEloquentUsingWrongDatabaseCredential } } } - -class User extends FoundationUser -{ - // -} diff --git a/tests/Integration/Auth/AuthenticationTest.php b/tests/Integration/Auth/AuthenticationTest.php index 388f23d030be..cd65dd793145 100644 --- a/tests/Integration/Auth/AuthenticationTest.php +++ b/tests/Integration/Auth/AuthenticationTest.php @@ -13,6 +13,7 @@ use Illuminate\Auth\SessionGuard; use Illuminate\Database\Schema\Blueprint; use Illuminate\Events\Dispatcher; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Schema; @@ -20,27 +21,42 @@ use Illuminate\Support\Testing\Fakes\EventFake; use Illuminate\Tests\Integration\Auth\Fixtures\AuthenticationTestUser; use InvalidArgumentException; +use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\TestCase; +#[WithMigration] class AuthenticationTest extends TestCase { - protected function getEnvironmentSetUp($app) + use RefreshDatabase; + + protected function defineEnvironment($app) { - $app['config']->set('auth.providers.users.model', AuthenticationTestUser::class); + $app['config']->set([ + 'auth.providers.users.model' => AuthenticationTestUser::class, + 'hashing.driver' => 'bcrypt', + ]); + } - $app['config']->set('hashing', ['driver' => 'bcrypt']); + protected function defineRoutes($router) + { + $router->get('basic', function () { + return $this->app['auth']->guard()->basic() + ?: $this->app['auth']->user()->toJson(); + }); + + $router->get('basicWithCondition', function () { + return $this->app['auth']->guard()->basic('email', ['is_active' => true]) + ?: $this->app['auth']->user()->toJson(); + }); } - protected function setUp(): void + protected function afterRefreshingDatabase() { - parent::setUp(); - - Schema::create('users', function (Blueprint $table) { - $table->increments('id'); - $table->string('email'); - $table->string('username'); - $table->string('password'); - $table->string('remember_token')->default(null)->nullable(); + Schema::table('users', function (Blueprint $table) { + $table->renameColumn('name', 'username'); + }); + + Schema::table('users', function (Blueprint $table) { $table->tinyInteger('is_active')->default(0); }); @@ -50,16 +66,6 @@ protected function setUp(): void 'password' => bcrypt('password'), 'is_active' => true, ]); - - $this->app->make('router')->get('basic', function () { - return $this->app['auth']->guard()->basic() - ?: $this->app['auth']->user()->toJson(); - }); - - $this->app->make('router')->get('basicWithCondition', function () { - return $this->app['auth']->guard()->basic('email', ['is_active' => true]) - ?: $this->app['auth']->user()->toJson(); - }); } public function testBasicAuthProtectsRoute() diff --git a/tests/Integration/Auth/ForgotPasswordTest.php b/tests/Integration/Auth/ForgotPasswordTest.php index 46a682c229d8..1bdee928b130 100644 --- a/tests/Integration/Auth/ForgotPasswordTest.php +++ b/tests/Integration/Auth/ForgotPasswordTest.php @@ -3,16 +3,21 @@ namespace Illuminate\Tests\Integration\Auth; use Illuminate\Auth\Notifications\ResetPassword; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Password; use Illuminate\Support\Str; use Illuminate\Tests\Integration\Auth\Fixtures\AuthenticationTestUser; +use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; +#[WithMigration] class ForgotPasswordTest extends TestCase { + use RefreshDatabase; + protected function tearDown(): void { ResetPassword::$createUrlCallback = null; @@ -27,11 +32,6 @@ protected function defineEnvironment($app) $app['config']->set('auth.providers.users.model', AuthenticationTestUser::class); } - protected function defineDatabaseMigrations() - { - $this->loadLaravelMigrations(); - } - protected function defineRoutes($router) { $router->get('password/reset/{token}', function ($token) { diff --git a/tests/Integration/Auth/ForgotPasswordWithoutDefaultRoutesTest.php b/tests/Integration/Auth/ForgotPasswordWithoutDefaultRoutesTest.php index e34dcd78b82d..f5f680beb1bc 100644 --- a/tests/Integration/Auth/ForgotPasswordWithoutDefaultRoutesTest.php +++ b/tests/Integration/Auth/ForgotPasswordWithoutDefaultRoutesTest.php @@ -3,16 +3,21 @@ namespace Illuminate\Tests\Integration\Auth; use Illuminate\Auth\Notifications\ResetPassword; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\Notification; use Illuminate\Support\Facades\Password; use Illuminate\Support\Str; use Illuminate\Tests\Integration\Auth\Fixtures\AuthenticationTestUser; +use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\Factories\UserFactory; use Orchestra\Testbench\TestCase; +#[WithMigration] class ForgotPasswordWithoutDefaultRoutesTest extends TestCase { + use RefreshDatabase; + protected function tearDown(): void { ResetPassword::$createUrlCallback = null; @@ -27,11 +32,6 @@ protected function defineEnvironment($app) $app['config']->set('auth.providers.users.model', AuthenticationTestUser::class); } - protected function defineDatabaseMigrations() - { - $this->loadLaravelMigrations(); - } - protected function defineRoutes($router) { $router->get('custom/password/reset/{token}', function ($token) { From a2d70b5a1e61b3c6b3c73c813779cd1b0a518150 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 21 Mar 2024 16:47:19 +0800 Subject: [PATCH 199/325] [10.x] Test Improvements for Encryption, Events, Filesystem, Foundation, Redis & Session tests (#50686) * [10.x] Test Improvements for Encryption, Events, Filesystem, Redis & Session tests Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- .../Integration/Encryption/EncryptionTest.php | 13 +---- tests/Integration/Events/EventFakeTest.php | 11 ++-- tests/Integration/Events/ListenerTest.php | 2 +- .../ShouldDispatchAfterCommitEventTest.php | 3 +- .../Integration/Filesystem/FilesystemTest.php | 5 +- tests/Integration/Filesystem/StorageTest.php | 5 +- .../Foundation/MaintenanceModeTest.php | 11 ++-- .../InteractsWithAuthenticationTest.php | 52 ++++++------------- .../Redis/PredisConnectionTest.php | 7 +-- .../Session/CookieSessionHandlerTest.php | 2 +- .../Session/SessionPersistenceTest.php | 2 +- 11 files changed, 36 insertions(+), 77 deletions(-) diff --git a/tests/Integration/Encryption/EncryptionTest.php b/tests/Integration/Encryption/EncryptionTest.php index 1ef8dab14193..d0a281bc292c 100644 --- a/tests/Integration/Encryption/EncryptionTest.php +++ b/tests/Integration/Encryption/EncryptionTest.php @@ -3,22 +3,13 @@ namespace Illuminate\Tests\Integration\Encryption; use Illuminate\Encryption\Encrypter; -use Illuminate\Encryption\EncryptionServiceProvider; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; use RuntimeException; +#[WithConfig('app.key', 'base64:IUHRqAQ99pZ0A1MPjbuv1D6ff3jxv0GIvS2qIW4JNU4=')] class EncryptionTest extends TestCase { - protected function getEnvironmentSetUp($app) - { - $app['config']->set('app.key', 'base64:IUHRqAQ99pZ0A1MPjbuv1D6ff3jxv0GIvS2qIW4JNU4='); - } - - protected function getPackageProviders($app) - { - return [EncryptionServiceProvider::class]; - } - public function testEncryptionProviderBind() { $this->assertInstanceOf(Encrypter::class, $this->app->make('encrypter')); diff --git a/tests/Integration/Events/EventFakeTest.php b/tests/Integration/Events/EventFakeTest.php index a7e9b97cf096..b6693eed94bc 100644 --- a/tests/Integration/Events/EventFakeTest.php +++ b/tests/Integration/Events/EventFakeTest.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\Events\ShouldDispatchAfterCommit; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Foundation\Testing\LazilyRefreshDatabase; use Illuminate\Support\Arr; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; @@ -16,10 +17,10 @@ class EventFakeTest extends TestCase { - protected function setUp(): void - { - parent::setUp(); + use LazilyRefreshDatabase; + protected function afterRefreshingDatabase() + { Schema::create('posts', function (Blueprint $table) { $table->increments('id'); $table->string('title'); @@ -28,11 +29,9 @@ protected function setUp(): void }); } - protected function tearDown(): void + protected function beforeRefreshingDatabase() { Schema::dropIfExists('posts'); - - parent::tearDown(); } public function testNonFakedEventGetsProperlyDispatched() diff --git a/tests/Integration/Events/ListenerTest.php b/tests/Integration/Events/ListenerTest.php index 76cdcb55c691..490117eef174 100644 --- a/tests/Integration/Events/ListenerTest.php +++ b/tests/Integration/Events/ListenerTest.php @@ -14,7 +14,7 @@ protected function tearDown(): void ListenerTestListener::$ran = false; ListenerTestListenerAfterCommit::$ran = false; - m::close(); + parent::tearDown(); } public function testClassListenerRunsNormallyIfNoTransactions() diff --git a/tests/Integration/Events/ShouldDispatchAfterCommitEventTest.php b/tests/Integration/Events/ShouldDispatchAfterCommitEventTest.php index 6b11d4f9916e..5ec65c1ed338 100644 --- a/tests/Integration/Events/ShouldDispatchAfterCommitEventTest.php +++ b/tests/Integration/Events/ShouldDispatchAfterCommitEventTest.php @@ -5,7 +5,6 @@ use Illuminate\Contracts\Events\ShouldDispatchAfterCommit; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; -use Mockery as m; use Orchestra\Testbench\TestCase; class ShouldDispatchAfterCommitEventTest extends TestCase @@ -16,7 +15,7 @@ protected function tearDown(): void ShouldDispatchAfterCommitTestEvent::$ran = false; AnotherShouldDispatchAfterCommitTestEvent::$ran = false; - m::close(); + parent::tearDown(); } public function testEventIsDispatchedIfThereIsNoTransaction() diff --git a/tests/Integration/Filesystem/FilesystemTest.php b/tests/Integration/Filesystem/FilesystemTest.php index 8bd38fe309fe..50d8965c82c6 100644 --- a/tests/Integration/Filesystem/FilesystemTest.php +++ b/tests/Integration/Filesystem/FilesystemTest.php @@ -4,11 +4,10 @@ use Illuminate\Support\Facades\File; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; use Symfony\Component\Process\Process; -/** - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|DAR')] class FilesystemTest extends TestCase { protected $stubFile; diff --git a/tests/Integration/Filesystem/StorageTest.php b/tests/Integration/Filesystem/StorageTest.php index 4193a629e178..90ba8fbfe58e 100644 --- a/tests/Integration/Filesystem/StorageTest.php +++ b/tests/Integration/Filesystem/StorageTest.php @@ -5,11 +5,10 @@ use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Storage; use Orchestra\Testbench\TestCase; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; use Symfony\Component\Process\Process; -/** - * @requires OS Linux|Darwin - */ +#[RequiresOperatingSystem('Linux|DAR')] class StorageTest extends TestCase { protected $stubFile; diff --git a/tests/Integration/Foundation/MaintenanceModeTest.php b/tests/Integration/Foundation/MaintenanceModeTest.php index 493934b6dd4a..377bff02858f 100644 --- a/tests/Integration/Foundation/MaintenanceModeTest.php +++ b/tests/Integration/Foundation/MaintenanceModeTest.php @@ -19,16 +19,15 @@ class MaintenanceModeTest extends TestCase { protected function setUp(): void { + $this->beforeApplicationDestroyed(function () { + @unlink(storage_path('framework/down')); + }); + parent::setUp(); $this->withoutMiddleware(TestbenchPreventRequestsDuringMaintenance::class); } - protected function tearDown(): void - { - @unlink(storage_path('framework/down')); - } - public function testBasicMaintenanceModeResponse() { file_put_contents(storage_path('framework/down'), json_encode([ @@ -172,8 +171,6 @@ public function testCanCreateBypassCookies() Carbon::setTestNow(now()->addMonths(6)); $this->assertFalse(MaintenanceModeBypassCookie::isValid($cookie->getValue(), 'test-key')); - - Carbon::setTestNow(null); } public function testDispatchEventWhenMaintenanceModeIsEnabled() diff --git a/tests/Integration/Foundation/Testing/Concerns/InteractsWithAuthenticationTest.php b/tests/Integration/Foundation/Testing/Concerns/InteractsWithAuthenticationTest.php index 01e97f5b0cd4..2ccaa6775b94 100644 --- a/tests/Integration/Foundation/Testing/Concerns/InteractsWithAuthenticationTest.php +++ b/tests/Integration/Foundation/Testing/Concerns/InteractsWithAuthenticationTest.php @@ -3,19 +3,22 @@ namespace Illuminate\Tests\Integration\Foundation\Testing\Concerns; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Foundation\Auth\User as Authenticatable; +use Illuminate\Foundation\Auth\User; +use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Schema; +use Orchestra\Testbench\Attributes\WithMigration; use Orchestra\Testbench\TestCase; +#[WithMigration] class InteractsWithAuthenticationTest extends TestCase { - protected function getEnvironmentSetUp($app) - { - $app['config']->set('auth.providers.users.model', AuthenticationTestUser::class); + use RefreshDatabase; + protected function defineEnvironment($app) + { $app['config']->set('auth.guards.api', [ 'driver' => 'token', 'provider' => 'users', @@ -23,20 +26,17 @@ protected function getEnvironmentSetUp($app) ]); } - protected function setUp(): void + protected function afterRefreshingDatabase() { - parent::setUp(); + Schema::table('users', function (Blueprint $table) { + $table->renameColumn('name', 'username'); + }); - Schema::create('users', function (Blueprint $table) { - $table->increments('id'); - $table->string('email'); - $table->string('username'); - $table->string('password'); - $table->string('remember_token')->default(null)->nullable(); + Schema::table('users', function (Blueprint $table) { $table->tinyInteger('is_active')->default(0); }); - AuthenticationTestUser::create([ + User::forceCreate([ 'username' => 'taylorotwell', 'email' => 'taylorotwell@laravel.com', 'password' => bcrypt('password'), @@ -50,7 +50,7 @@ public function testActingAsIsProperlyHandledForSessionAuth() return 'Hello '.$request->user()->username; })->middleware(['auth']); - $user = AuthenticationTestUser::where('username', '=', 'taylorotwell')->first(); + $user = User::where('username', '=', 'taylorotwell')->first(); $this->actingAs($user) ->get('/me') @@ -68,7 +68,7 @@ public function testActingAsIsProperlyHandledForAuthViaRequest() return $request->user(); }); - $user = AuthenticationTestUser::where('username', '=', 'taylorotwell')->first(); + $user = User::where('username', '=', 'taylorotwell')->first(); $this->actingAs($user, 'api') ->get('/me') @@ -76,25 +76,3 @@ public function testActingAsIsProperlyHandledForAuthViaRequest() ->assertSeeText('Hello taylorotwell'); } } - -class AuthenticationTestUser extends Authenticatable -{ - public $table = 'users'; - public $timestamps = false; - - /** - * The attributes that are mass assignable. - * - * @var string[] - */ - protected $guarded = []; - - /** - * The attributes that should be hidden for arrays. - * - * @var string[] - */ - protected $hidden = [ - 'password', 'remember_token', - ]; -} diff --git a/tests/Integration/Redis/PredisConnectionTest.php b/tests/Integration/Redis/PredisConnectionTest.php index e25dbbc8fe4e..2ec51d21521a 100644 --- a/tests/Integration/Redis/PredisConnectionTest.php +++ b/tests/Integration/Redis/PredisConnectionTest.php @@ -6,17 +6,14 @@ use Illuminate\Redis\Events\CommandExecuted; use Illuminate\Support\Facades\Event; use Mockery as m; +use Orchestra\Testbench\Attributes\WithConfig; use Orchestra\Testbench\TestCase; use Predis\Client; use Predis\Command\Argument\Search\SearchArguments; +#[WithConfig('database.redis.client', 'predis')] class PredisConnectionTest extends TestCase { - protected function defineEnvironment($app) - { - $app->get('config')->set('database.redis.client', 'predis'); - } - public function testPredisCanEmitEventWithArrayableArgumentObject() { if (! class_exists(SearchArguments::class)) { diff --git a/tests/Integration/Session/CookieSessionHandlerTest.php b/tests/Integration/Session/CookieSessionHandlerTest.php index e7307d71421a..896cf25cf5c5 100644 --- a/tests/Integration/Session/CookieSessionHandlerTest.php +++ b/tests/Integration/Session/CookieSessionHandlerTest.php @@ -20,7 +20,7 @@ public function testCookieSessionDriverCookiesCanExpireOnClose() $this->assertEquals(0, $sessionValueCookie->getExpiresTime()); } - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { $app['config']->set('app.key', Str::random(32)); $app['config']->set('session.driver', 'cookie'); diff --git a/tests/Integration/Session/SessionPersistenceTest.php b/tests/Integration/Session/SessionPersistenceTest.php index d911bdffae0e..6253b79eb766 100644 --- a/tests/Integration/Session/SessionPersistenceTest.php +++ b/tests/Integration/Session/SessionPersistenceTest.php @@ -31,7 +31,7 @@ public function testSessionIsPersistedEvenIfExceptionIsThrownFromRoute() $this->assertTrue($handler->written); } - protected function getEnvironmentSetUp($app) + protected function defineEnvironment($app) { $app->instance( ExceptionHandler::class, From a70b776e3860ca57203d79aa2317985cde5682f8 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 21 Mar 2024 21:10:17 +0800 Subject: [PATCH 200/325] [10.x] Fix command alias registration and usage (#50695) * wip Signed-off-by: Mior Muhammad Zaki * Fix command alias parsing Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update tests.yml * Update tests.yml * Update tests.yml * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Tim MacDonald --- .github/workflows/databases.yml | 25 ++++++---- .github/workflows/facades.yml | 2 - .github/workflows/queues.yml | 20 ++++---- .github/workflows/static-analysis.yml | 5 +- .github/workflows/tests.yml | 10 ++-- src/Illuminate/Console/Application.php | 4 +- .../Console/ConsoleApplicationTest.php | 47 ++++++++++++++++++- 7 files changed, 84 insertions(+), 29 deletions(-) diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index 41bb82da9666..754b43b8df99 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -29,8 +29,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -40,6 +38,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: @@ -74,8 +75,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -85,6 +84,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: @@ -119,8 +121,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -130,6 +130,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: @@ -165,8 +168,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -176,6 +177,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: @@ -209,8 +213,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -220,6 +222,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: diff --git a/.github/workflows/facades.yml b/.github/workflows/facades.yml index af8eb9b60223..93772d1427c7 100644 --- a/.github/workflows/facades.yml +++ b/.github/workflows/facades.yml @@ -19,8 +19,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/.github/workflows/queues.yml b/.github/workflows/queues.yml index ef08a648a1fe..1856f6b5b194 100644 --- a/.github/workflows/queues.yml +++ b/.github/workflows/queues.yml @@ -19,8 +19,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -30,6 +28,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: @@ -53,8 +54,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -64,6 +63,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: @@ -95,8 +97,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -106,6 +106,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: @@ -126,8 +129,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Download & Extract beanstalkd run: curl -L https://github.com/beanstalkd/beanstalkd/archive/refs/tags/v1.13.tar.gz | tar xz @@ -144,6 +145,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 76a0be9b42e5..9fb6fdfcf74f 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -21,8 +21,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -31,6 +29,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a4feecb7ce0..079f71a40337 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,8 +47,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -62,6 +60,9 @@ jobs: REDIS_CONFIGURE_OPTS: --enable-redis --enable-redis-igbinary --enable-redis-msgpack --enable-redis-lzf --with-liblzf --enable-redis-zstd --with-libzstd --enable-redis-lz4 --with-liblz4 REDIS_LIBS: liblz4-dev, liblzf-dev, libzstd-dev + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Set minimum PHP 8.1 versions uses: nick-fields/retry@v3 with: @@ -121,8 +122,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -132,6 +131,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Set Minimum PHP 8.1 Versions uses: nick-fields/retry@v3 with: diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 98536ce41eec..d880f8df63d3 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -237,7 +237,9 @@ protected function addToParent(SymfonyCommand $command) public function resolve($command) { if (is_subclass_of($command, SymfonyCommand::class) && ($commandName = $command::getDefaultName())) { - $this->commandMap[$commandName] = $command; + foreach (explode('|', $commandName) as $name) { + $this->commandMap[$name] = $command; + } return null; } diff --git a/tests/Integration/Console/ConsoleApplicationTest.php b/tests/Integration/Console/ConsoleApplicationTest.php index 9daafb57c89a..bd54013c0649 100644 --- a/tests/Integration/Console/ConsoleApplicationTest.php +++ b/tests/Integration/Console/ConsoleApplicationTest.php @@ -2,20 +2,27 @@ namespace Illuminate\Tests\Integration\Console; +use Illuminate\Console\Application as Artisan; use Illuminate\Console\Command; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Contracts\Console\Kernel; use Illuminate\Foundation\Console\QueuedCommand; use Illuminate\Support\Facades\Queue; use Orchestra\Testbench\TestCase; +use Symfony\Component\Console\Attribute\AsCommand; class ConsoleApplicationTest extends TestCase { protected function setUp(): void { - parent::setUp(); + Artisan::starting(function ($artisan) { + $artisan->resolveCommands([ + FooCommandStub::class, + ZondaCommandStub::class, + ]); + }); - $this->app[Kernel::class]->registerCommand(new FooCommandStub); + parent::setUp(); } public function testArtisanCallUsingCommandName() @@ -25,6 +32,13 @@ public function testArtisanCallUsingCommandName() ])->assertExitCode(0); } + public function testArtisanCallUsingCommandNameAliases() + { + $this->artisan('app:foobar', [ + 'id' => 1, + ])->assertExitCode(0); + } + public function testArtisanCallUsingCommandClass() { $this->artisan(FooCommandStub::class, [ @@ -32,6 +46,20 @@ public function testArtisanCallUsingCommandClass() ])->assertExitCode(0); } + public function testArtisanCallUsingCommandNameUsingAsCommandAttribute() + { + $this->artisan('zonda', [ + 'id' => 1, + ])->assertExitCode(0); + } + + public function testArtisanCallUsingCommandNameAliasesUsingAsCommandAttribute() + { + $this->artisan('app:zonda', [ + 'id' => 1, + ])->assertExitCode(0); + } + public function testArtisanCallNow() { $exitCode = $this->artisan('foo:bar', [ @@ -86,6 +114,21 @@ class FooCommandStub extends Command { protected $signature = 'foo:bar {id}'; + protected $aliases = ['app:foobar']; + + public function handle() + { + // + } +} + +#[AsCommand(name: 'zonda', aliases: ['app:zonda'])] +class ZondaCommandStub extends Command +{ + protected $signature = 'zonda {id}'; + + protected $aliases = ['app:zonda']; + public function handle() { // From 7e0701bf59cb76a51f7c1f7bea51c0c0c29c0b72 Mon Sep 17 00:00:00 2001 From: driesvints Date: Thu, 21 Mar 2024 13:36:36 +0000 Subject: [PATCH 201/325] Update version to v10.48.4 --- 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 a35b35c10664..30d60b3f4976 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.3'; + const VERSION = '10.48.4'; /** * The base path for the Laravel installation. From 0dbc3749e34f0eb39a1ab10f1ad940a00c5e9f77 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 21 Mar 2024 15:09:59 +0100 Subject: [PATCH 202/325] Update CHANGELOG.md --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9c0d153a75f..7bfa90903619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.3...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.4...10.x) + +## [v10.48.4](https://github.com/laravel/framework/compare/v10.48.3...v10.48.4) - 2024-03-21 + +* [10.x] Fix `Collection::concat()` return type by @axlon in https://github.com/laravel/framework/pull/50669 +* [10.x] Fix command alias registration and usage by @crynobone in https://github.com/laravel/framework/pull/50695 ## [v10.48.3](https://github.com/laravel/framework/compare/v10.48.2...v10.48.3) - 2024-03-15 From cfedb2300c6f7e6b45db5c36f8c4389c79305db9 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 21 Mar 2024 15:11:30 +0100 Subject: [PATCH 203/325] Explicitly set `composer config version` to avoid pulling all branches (#50701) and tags for GitHub Actions Signed-off-by: Mior Muhammad Zaki Co-authored-by: Mior Muhammad Zaki --- .github/workflows/facades.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/facades.yml b/.github/workflows/facades.yml index 93772d1427c7..891cb7d1d10b 100644 --- a/.github/workflows/facades.yml +++ b/.github/workflows/facades.yml @@ -28,6 +28,9 @@ jobs: tools: composer:v2 coverage: none + - name: Set Framework version + run: composer config version "10.x-dev" + - name: Install dependencies uses: nick-fields/retry@v3 with: From 772b861a7012b1cf6362759f828b53d38bed0532 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 26 Mar 2024 16:54:48 +0800 Subject: [PATCH 204/325] [10.x] Test Improvements (#50763) Add tests to verify using `auth()->logoutOtherDevices()` will rehash the password. Signed-off-by: Mior Muhammad Zaki --- .../Auth/RehashOnLogoutOtherDevicesTest.php | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/Integration/Auth/RehashOnLogoutOtherDevicesTest.php diff --git a/tests/Integration/Auth/RehashOnLogoutOtherDevicesTest.php b/tests/Integration/Auth/RehashOnLogoutOtherDevicesTest.php new file mode 100644 index 000000000000..8e704ff33345 --- /dev/null +++ b/tests/Integration/Auth/RehashOnLogoutOtherDevicesTest.php @@ -0,0 +1,46 @@ +post('logout', function (Request $request) { + auth()->logoutOtherDevices($request->input('password')); + + return response()->noContent(); + })->middleware(['web', 'auth']); + } + + public function testItRehashThePasswordUsingLogoutOtherDevices() + { + $this->withoutExceptionHandling(); + + $user = UserFactory::new()->create(); + + $password = $user->password; + + $this->actingAs($user); + + $this->post('logout', [ + 'password' => 'password', + ])->assertStatus(204); + + $user->refresh(); + + $this->assertNotSame($password, $user->password); + } +} From 7bc70f29ba7a8cee2cac1bba758d968ba4927fdf Mon Sep 17 00:00:00 2001 From: Dima Kondrashov Date: Thu, 28 Mar 2024 15:00:29 +0100 Subject: [PATCH 205/325] Detect lost redis connection (#50812) --- src/Illuminate/Database/DetectsLostConnections.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index 32492eb0729c..f946f35dc59c 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -71,6 +71,7 @@ protected function causedByLostConnection(Throwable $e) 'SQLSTATE[HY000] [2002] The requested address is not valid in its context', 'SQLSTATE[HY000] [2002] A socket operation was attempted to an unreachable network', 'SQLSTATE[HY000]: General error: 3989', + 'went away', ]); } } From a8a40d9bb151f42b1d6cfe8bd05a3f4a39fec728 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 1 Apr 2024 08:24:24 +0800 Subject: [PATCH 206/325] [10.x] Test Improvements (#50864) Use `#[RequiresOperatingSystem('Linux|DAR')]` instead of checking `windows_os()` Signed-off-by: Mior Muhammad Zaki --- tests/Process/ProcessTest.php | 61 ++++++++--------------------------- 1 file changed, 13 insertions(+), 48 deletions(-) diff --git a/tests/Process/ProcessTest.php b/tests/Process/ProcessTest.php index 4557004e1d1a..4c914b2e9698 100644 --- a/tests/Process/ProcessTest.php +++ b/tests/Process/ProcessTest.php @@ -7,6 +7,7 @@ use Illuminate\Process\Exceptions\ProcessTimedOutException; use Illuminate\Process\Factory; use OutOfBoundsException; +use PHPUnit\Framework\Attributes\RequiresOperatingSystem; use PHPUnit\Framework\TestCase; use RuntimeException; @@ -424,12 +425,9 @@ public function testFakeProcessesDontThrowIfFalse() $this->assertTrue(true); } + #[RequiresOperatingSystem('Linux|DAR')] public function testRealProcessesCanHaveErrorOutput() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $factory = new Factory; $result = $factory->path(__DIR__)->run('echo "Hello World" >&2; exit 1;'); @@ -455,12 +453,9 @@ public function testFakeProcessesCanThrowWithoutOutput() $result->throw(); } + #[RequiresOperatingSystem('Linux|DAR')] public function testRealProcessesCanThrowWithoutOutput() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $this->expectException(ProcessFailedException::class); $this->expectExceptionMessage(<<<'EOT' The command "exit 1;" failed. @@ -496,12 +491,9 @@ public function testFakeProcessesCanThrowWithErrorOutput() $result->throw(); } + #[RequiresOperatingSystem('Linux|DAR')] public function testRealProcessesCanThrowWithErrorOutput() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $this->expectException(ProcessFailedException::class); $this->expectExceptionMessage(<<<'EOT' The command "echo "Hello World" >&2; exit 1;" failed. @@ -541,12 +533,9 @@ public function testFakeProcessesCanThrowWithOutput() $result->throw(); } + #[RequiresOperatingSystem('Linux|DAR')] public function testRealProcessesCanThrowWithOutput() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $this->expectException(ProcessFailedException::class); $this->expectExceptionMessage(<<<'EOT' The command "echo "Hello World" >&1; exit 1;" failed. @@ -565,12 +554,9 @@ public function testRealProcessesCanThrowWithOutput() $result->throw(); } + #[RequiresOperatingSystem('Linux|DAR')] public function testRealProcessesCanTimeout() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $this->expectException(ProcessTimedOutException::class); $this->expectExceptionMessage( 'The process "sleep 2; exit 1;" exceeded the timeout of 1 seconds.' @@ -582,12 +568,9 @@ public function testRealProcessesCanTimeout() $result->throw(); } + #[RequiresOperatingSystem('Linux|DAR')] public function testRealProcessesCanThrowIfTrue() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $this->expectException(ProcessFailedException::class); $factory = new Factory; @@ -596,12 +579,9 @@ public function testRealProcessesCanThrowIfTrue() $result->throwIf(true); } + #[RequiresOperatingSystem('Linux|DAR')] public function testRealProcessesDoesntThrowIfFalse() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $factory = new Factory; $result = $factory->path(__DIR__)->run('echo "Hello World" >&2; exit 1;'); @@ -610,24 +590,18 @@ public function testRealProcessesDoesntThrowIfFalse() $this->assertTrue(true); } + #[RequiresOperatingSystem('Linux|DAR')] public function testRealProcessesCanUseStandardInput() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $factory = new Factory(); $result = $factory->input('foobar')->run('cat'); $this->assertSame('foobar', $result->output()); } + #[RequiresOperatingSystem('Linux|DAR')] public function testProcessPipe() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $factory = new Factory; $factory->fake([ 'cat *' => "Hello, world\nfoo\nbar", @@ -641,12 +615,9 @@ public function testProcessPipe() $this->assertSame("foo\n", $pipe->output()); } + #[RequiresOperatingSystem('Linux|DAR')] public function testProcessPipeFailed() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $factory = new Factory; $factory->fake([ 'cat *' => $factory->result(exitCode: 1), @@ -660,12 +631,9 @@ public function testProcessPipeFailed() $this->assertTrue($pipe->failed()); } + #[RequiresOperatingSystem('Linux|DAR')] public function testProcessSimplePipe() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $factory = new Factory; $factory->fake([ 'cat *' => "Hello, world\nfoo\nbar", @@ -679,12 +647,9 @@ public function testProcessSimplePipe() $this->assertSame("foo\n", $pipe->output()); } + #[RequiresOperatingSystem('Linux|DAR')] public function testProcessSimplePipeFailed() { - if (windows_os()) { - $this->markTestSkipped('Requires Linux.'); - } - $factory = new Factory; $factory->fake([ 'cat *' => $factory->result(exitCode: 1), From 2298bc341f3edcf95aa54ac03687b763414dce76 Mon Sep 17 00:00:00 2001 From: Apos Spanos <5887954+apspan@users.noreply.github.com> Date: Thu, 4 Apr 2024 20:45:10 +0300 Subject: [PATCH 207/325] [10.x] Laravel 10x optional withSize for hasTable (#50888) * make withsize optional for SQLiteBuilder's getTables * refactoring SQLiteBuilder::getTables Signed-off-by: Apos Spanos * correcting SQLiteBuilder::getTables Signed-off-by: Apos Spanos --------- Signed-off-by: Apos Spanos --- src/Illuminate/Database/Schema/Builder.php | 2 +- src/Illuminate/Database/Schema/SQLiteBuilder.php | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 51b97f690618..4a8839442951 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -162,7 +162,7 @@ public function hasTable($table) { $table = $this->connection->getTablePrefix().$table; - foreach ($this->getTables() as $value) { + foreach ($this->getTables(false) as $value) { if (strtolower($table) === strtolower($value['name'])) { return true; } diff --git a/src/Illuminate/Database/Schema/SQLiteBuilder.php b/src/Illuminate/Database/Schema/SQLiteBuilder.php index 8ae272d767b6..e7d6e8c905eb 100644 --- a/src/Illuminate/Database/Schema/SQLiteBuilder.php +++ b/src/Illuminate/Database/Schema/SQLiteBuilder.php @@ -34,16 +34,17 @@ public function dropDatabaseIfExists($name) /** * Get the tables for the database. * + * @param bool $withSize * @return array */ - public function getTables() + public function getTables($withSize = true) { - $withSize = false; - - try { - $withSize = $this->connection->scalar($this->grammar->compileDbstatExists()); - } catch (QueryException $e) { - // + if ($withSize) { + try { + $withSize = $this->connection->scalar($this->grammar->compileDbstatExists()); + } catch (QueryException $e) { + $withSize = false; + } } return $this->connection->getPostProcessor()->processTables( From 5d3fa1cd53d9c88fc61fe9bda2b960259caa300d Mon Sep 17 00:00:00 2001 From: Daniel Polito Date: Sun, 7 Apr 2024 14:47:00 -0300 Subject: [PATCH 208/325] [10.x] Add `serializeAndRestore()` to `NotificationFake` (#50935) * Add serializeAndRestore() to NotificationFake * Fix Style * Fix * formatting --------- Co-authored-by: Taylor Otwell --- .../Testing/Fakes/NotificationFake.php | 36 ++++++++++++++++++- .../SupportTestingNotificationFakeTest.php | 31 ++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Testing/Fakes/NotificationFake.php b/src/Illuminate/Support/Testing/Fakes/NotificationFake.php index df00c465ff3c..ee610a401069 100644 --- a/src/Illuminate/Support/Testing/Fakes/NotificationFake.php +++ b/src/Illuminate/Support/Testing/Fakes/NotificationFake.php @@ -6,6 +6,7 @@ use Exception; use Illuminate\Contracts\Notifications\Dispatcher as NotificationDispatcher; use Illuminate\Contracts\Notifications\Factory as NotificationFactory; +use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Notifications\AnonymousNotifiable; use Illuminate\Support\Collection; @@ -32,6 +33,13 @@ class NotificationFake implements Fake, NotificationDispatcher, NotificationFact */ public $locale; + /** + * Indicates if notifications should be serialized and restored when pushed to the queue. + * + * @var bool + */ + protected $serializeAndRestore = false; + /** * Assert if a notification was sent on-demand based on a truth-test callback. * @@ -313,7 +321,9 @@ public function sendNow($notifiables, $notification, array $channels = null) } $this->notifications[get_class($notifiable)][$notifiable->getKey()][get_class($notification)][] = [ - 'notification' => $notification, + 'notification' => $this->serializeAndRestore && $notification instanceof ShouldQueue + ? $this->serializeAndRestoreNotification($notification) + : $notification, 'channels' => $notifiableChannels, 'notifiable' => $notifiable, 'locale' => $notification->locale ?? $this->locale ?? value(function () use ($notifiable) { @@ -349,6 +359,30 @@ public function locale($locale) return $this; } + /** + * Specify if notification should be serialized and restored when being "pushed" to the queue. + * + * @param bool $serializeAndRestore + * @return $this + */ + public function serializeAndRestore(bool $serializeAndRestore = true) + { + $this->serializeAndRestore = $serializeAndRestore; + + return $this; + } + + /** + * Serialize and unserialize the notification to simulate the queueing process. + * + * @param mixed $notification + * @return mixed + */ + protected function serializeAndRestoreNotification($notification) + { + return unserialize(serialize($notification)); + } + /** * Get the notifications that have been sent. * diff --git a/tests/Support/SupportTestingNotificationFakeTest.php b/tests/Support/SupportTestingNotificationFakeTest.php index 878296c18e42..10fce3dbde54 100644 --- a/tests/Support/SupportTestingNotificationFakeTest.php +++ b/tests/Support/SupportTestingNotificationFakeTest.php @@ -3,6 +3,8 @@ namespace Illuminate\Tests\Support; use Exception; +use Illuminate\Bus\Queueable; +use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Contracts\Translation\HasLocalePreference; use Illuminate\Foundation\Auth\User; use Illuminate\Notifications\AnonymousNotifiable; @@ -221,6 +223,16 @@ public function testAssertSentToWhenNotifiableHasFalsyShouldSend() $this->fake->assertNotSentTo($user, NotificationWithFalsyShouldSendStub::class); } + + public function testAssertItCanSerializeAndRestoreNotifications() + { + $this->fake->serializeAndRestore(); + $this->fake->send($this->user, new NotificationWithSerialization('hello')); + + $this->fake->assertSentTo($this->user, NotificationWithSerialization::class, function ($notification) { + return $notification->value === 'hello-serialized-unserialized'; + }); + } } class NotificationStub extends Notification @@ -256,3 +268,22 @@ public function preferredLocale() return 'au'; } } + +class NotificationWithSerialization extends NotificationStub implements ShouldQueue +{ + use Queueable; + + public function __construct(public $value) + { + } + + public function __serialize(): array + { + return ['value' => $this->value.'-serialized']; + } + + public function __unserialize(array $data): void + { + $this->value = $data['value'].'-unserialized'; + } +} From 7ba6047a3e13ea0ce4d441f5b53c6b131da36d2b Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Sun, 7 Apr 2024 17:47:33 +0000 Subject: [PATCH 209/325] Update facade docblocks --- src/Illuminate/Support/Facades/Notification.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/Facades/Notification.php b/src/Illuminate/Support/Facades/Notification.php index a588bd5467ed..8b30997e7923 100644 --- a/src/Illuminate/Support/Facades/Notification.php +++ b/src/Illuminate/Support/Facades/Notification.php @@ -31,6 +31,7 @@ * @method static void assertCount(int $expectedCount) * @method static \Illuminate\Support\Collection sent(mixed $notifiable, string $notification, callable|null $callback = null) * @method static bool hasSent(mixed $notifiable, string $notification) + * @method static \Illuminate\Support\Testing\Fakes\NotificationFake serializeAndRestore(bool $serializeAndRestore = true) * @method static array sentNotifications() * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) From 7abab3a8fe3d5504ba6def4b942d6b6aac50d012 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 8 Apr 2024 20:40:42 +0800 Subject: [PATCH 210/325] Test Improvements (#50960) * Test Improvements * wip --- tests/Integration/Foundation/Console/AboutCommandTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Integration/Foundation/Console/AboutCommandTest.php b/tests/Integration/Foundation/Console/AboutCommandTest.php index 6795d3bafeb2..d671ba8207c4 100644 --- a/tests/Integration/Foundation/Console/AboutCommandTest.php +++ b/tests/Integration/Foundation/Console/AboutCommandTest.php @@ -11,13 +11,13 @@ class AboutCommandTest extends TestCase { public function testItCanDisplayAboutCommandAsJson() { - $process = remote('about --json')->mustRun(); + $process = remote('about --json', ['APP_ENV' => 'local'])->mustRun(); tap(json_decode($process->getOutput(), true), function ($output) { Assert::assertArraySubset([ 'application_name' => 'Laravel', 'php_version' => PHP_VERSION, - 'environment' => 'testing', + 'environment' => 'local', 'debug_mode' => true, 'url' => 'localhost', 'maintenance_mode' => false, From a8b682e1371338848637f638aedaef2f7607d36f Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 9 Apr 2024 14:48:50 +0000 Subject: [PATCH 211/325] Update version to v10.48.5 --- 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 30d60b3f4976..9649f34af55b 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.4'; + const VERSION = '10.48.5'; /** * The base path for the Laravel installation. From bc2a249e24f27199b3b4825562f8c1d325f3c9d8 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 9 Apr 2024 14:55:13 +0000 Subject: [PATCH 212/325] Update CHANGELOG --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bfa90903619..12909848e4c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.4...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.5...10.x) + +## [v10.48.5](https://github.com/laravel/framework/compare/v10.48.4...v10.48.5) - 2024-04-09 + +### What's Changed + +* [10.x] Prevent Redis connection error report flood on queue worker by [@kasus](https://github.com/kasus) in https://github.com/laravel/framework/pull/50812 +* [10.x] Laravel 10x optional withSize for hasTable by [@apspan](https://github.com/apspan) in https://github.com/laravel/framework/pull/50888 +* [10.x] Add `serializeAndRestore()` to `NotificationFake` by [@dbpolito](https://github.com/dbpolito) in https://github.com/laravel/framework/pull/50935 ## [v10.48.4](https://github.com/laravel/framework/compare/v10.48.3...v10.48.4) - 2024-03-21 From cc039d5c211a6abe129b00ae5e1f319f83ea6a19 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 9 Apr 2024 16:57:08 +0200 Subject: [PATCH 213/325] Update CHANGELOG.md --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12909848e4c8..c52d6040c23e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,6 @@ ## [v10.48.5](https://github.com/laravel/framework/compare/v10.48.4...v10.48.5) - 2024-04-09 -### What's Changed - * [10.x] Prevent Redis connection error report flood on queue worker by [@kasus](https://github.com/kasus) in https://github.com/laravel/framework/pull/50812 * [10.x] Laravel 10x optional withSize for hasTable by [@apspan](https://github.com/apspan) in https://github.com/laravel/framework/pull/50888 * [10.x] Add `serializeAndRestore()` to `NotificationFake` by [@dbpolito](https://github.com/dbpolito) in https://github.com/laravel/framework/pull/50935 From b9055973009c1f5a4856e6d170fb28384a3285d2 Mon Sep 17 00:00:00 2001 From: Lonny Kapelushnik Date: Wed, 10 Apr 2024 08:11:27 -0600 Subject: [PATCH 214/325] [10.x] Added eachById and chunkByIdDesc to BelongsToMany (#50991) * Added failing test * Added eachById and chunkByIdDesc to BelongsToMany * Removed extra namespace --- .../Eloquent/Relations/BelongsToMany.php | 55 ++++++- ...baseEloquentBelongsToManyChunkByIdTest.php | 31 ++-- ...abaseEloquentBelongsToManyEachByIdTest.php | 134 ++++++++++++++++++ 3 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 tests/Database/DatabaseEloquentBelongsToManyEachByIdTest.php diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index c0459b4d7267..73152d7e5879 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -997,19 +997,66 @@ public function chunk($count, callable $callback) */ public function chunkById($count, callable $callback, $column = null, $alias = null) { - $this->prepareQueryBuilder(); + return $this->orderedChunkById($count, $callback, $column, $alias); + } + + /** + * Chunk the results of a query by comparing IDs in descending order. + * + * @param int $count + * @param callable $callback + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null) + { + return $this->orderedChunkById($count, $callback, $column, $alias, descending: true); + } + /** + * Execute a callback over each item while chunking by ID. + * + * @param callable $callback + * @param int $count + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function eachById(callable $callback, $count = 1000, $column = null, $alias = null) + { + return $this->chunkById($count, function ($results, $page) use ($callback, $count) { + foreach ($results as $key => $value) { + if ($callback($value, (($page - 1) * $count) + $key) === false) { + return false; + } + } + }, $column, $alias); + } + + /** + * Chunk the results of a query by comparing IDs in a given order. + * + * @param int $count + * @param callable $callback + * @param string|null $column + * @param string|null $alias + * @param bool $descending + * @return bool + */ + public function orderedChunkById($count, callable $callback, $column = null, $alias = null, $descending = false) + { $column ??= $this->getRelated()->qualifyColumn( $this->getRelatedKeyName() ); $alias ??= $this->getRelatedKeyName(); - return $this->query->chunkById($count, function ($results) use ($callback) { + return $this->prepareQueryBuilder()->orderedChunkById($count, function ($results, $page) use ($callback) { $this->hydratePivotRelation($results->all()); - return $callback($results); - }, $column, $alias); + return $callback($results, $page); + }, $column, $alias, $descending); } /** diff --git a/tests/Database/DatabaseEloquentBelongsToManyChunkByIdTest.php b/tests/Database/DatabaseEloquentBelongsToManyChunkByIdTest.php index f35c2f9a3ce9..0b9d8d51d93c 100644 --- a/tests/Database/DatabaseEloquentBelongsToManyChunkByIdTest.php +++ b/tests/Database/DatabaseEloquentBelongsToManyChunkByIdTest.php @@ -37,13 +37,14 @@ public function createSchema() }); $this->schema()->create('articles', function ($table) { - $table->increments('aid'); + $table->increments('id'); $table->string('title'); }); $this->schema()->create('article_user', function ($table) { + $table->increments('id'); $table->integer('article_id')->unsigned(); - $table->foreign('article_id')->references('aid')->on('articles'); + $table->foreign('article_id')->references('id')->on('articles'); $table->integer('user_id')->unsigned(); $table->foreign('user_id')->references('id')->on('users'); }); @@ -58,7 +59,22 @@ public function testBelongsToChunkById() $user->articles()->chunkById(1, function (Collection $collection) use (&$i) { $i++; - $this->assertEquals($i, $collection->first()->aid); + $this->assertEquals($i, $collection->first()->id); + }); + + $this->assertSame(3, $i); + } + + public function testBelongsToChunkByIdDesc() + { + $this->seedData(); + + $user = BelongsToManyChunkByIdTestTestUser::query()->first(); + $i = 0; + + $user->articles()->chunkByIdDesc(1, function (Collection $collection) use (&$i) { + $this->assertEquals(3 - $i, $collection->first()->id); + $i++; }); $this->assertSame(3, $i); @@ -83,9 +99,9 @@ protected function seedData() { $user = BelongsToManyChunkByIdTestTestUser::create(['id' => 1, 'email' => 'taylorotwell@gmail.com']); BelongsToManyChunkByIdTestTestArticle::query()->insert([ - ['aid' => 1, 'title' => 'Another title'], - ['aid' => 2, 'title' => 'Another title'], - ['aid' => 3, 'title' => 'Another title'], + ['id' => 1, 'title' => 'Another title'], + ['id' => 2, 'title' => 'Another title'], + ['id' => 3, 'title' => 'Another title'], ]); $user->articles()->sync([3, 1, 2]); @@ -126,10 +142,9 @@ public function articles() class BelongsToManyChunkByIdTestTestArticle extends Eloquent { - protected $primaryKey = 'aid'; protected $table = 'articles'; protected $keyType = 'string'; public $incrementing = false; public $timestamps = false; - protected $fillable = ['aid', 'title']; + protected $fillable = ['id', 'title']; } diff --git a/tests/Database/DatabaseEloquentBelongsToManyEachByIdTest.php b/tests/Database/DatabaseEloquentBelongsToManyEachByIdTest.php new file mode 100644 index 000000000000..0a2fe1e97a06 --- /dev/null +++ b/tests/Database/DatabaseEloquentBelongsToManyEachByIdTest.php @@ -0,0 +1,134 @@ +addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + ]); + + $db->bootEloquent(); + $db->setAsGlobal(); + + $this->createSchema(); + } + + /** + * Setup the database schema. + * + * @return void + */ + public function createSchema() + { + $this->schema()->create('users', function ($table) { + $table->increments('id'); + $table->string('email')->unique(); + }); + + $this->schema()->create('articles', function ($table) { + $table->increments('id'); + $table->string('title'); + }); + + $this->schema()->create('article_user', function ($table) { + $table->increments('id'); + $table->integer('article_id')->unsigned(); + $table->foreign('article_id')->references('id')->on('articles'); + $table->integer('user_id')->unsigned(); + $table->foreign('user_id')->references('id')->on('users'); + }); + } + + public function testBelongsToEachById() + { + $this->seedData(); + + $user = BelongsToManyEachByIdTestTestUser::query()->first(); + $i = 0; + + $user->articles()->eachById(function (BelongsToManyEachByIdTestTestArticle $model) use (&$i) { + $i++; + $this->assertEquals($i, $model->id); + }); + + $this->assertSame(3, $i); + } + + /** + * Tear down the database schema. + * + * @return void + */ + protected function tearDown(): void + { + $this->schema()->drop('users'); + $this->schema()->drop('articles'); + $this->schema()->drop('article_user'); + } + + /** + * Helpers... + */ + protected function seedData() + { + $user = BelongsToManyEachByIdTestTestUser::create(['id' => 1, 'email' => 'taylorotwell@gmail.com']); + BelongsToManyEachByIdTestTestArticle::query()->insert([ + ['id' => 1, 'title' => 'Another title'], + ['id' => 2, 'title' => 'Another title'], + ['id' => 3, 'title' => 'Another title'], + ]); + + $user->articles()->sync([3, 1, 2]); + } + + /** + * Get a database connection instance. + * + * @return \Illuminate\Database\ConnectionInterface + */ + protected function connection() + { + return Eloquent::getConnectionResolver()->connection(); + } + + /** + * Get a schema builder instance. + * + * @return \Illuminate\Database\Schema\Builder + */ + protected function schema() + { + return $this->connection()->getSchemaBuilder(); + } +} + +class BelongsToManyEachByIdTestTestUser extends Eloquent +{ + protected $table = 'users'; + protected $fillable = ['id', 'email']; + public $timestamps = false; + + public function articles() + { + return $this->belongsToMany(BelongsToManyEachByIdTestTestArticle::class, 'article_user', 'user_id', 'article_id'); + } +} + +class BelongsToManyEachByIdTestTestArticle extends Eloquent +{ + protected $table = 'articles'; + protected $keyType = 'string'; + public $incrementing = false; + public $timestamps = false; + protected $fillable = ['id', 'title']; +} From db3fe1b9492d4266b1611c79848d6bfb88c3981e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 10 Apr 2024 09:41:04 -0500 Subject: [PATCH 215/325] fix chunkByIdDesc --- .../Eloquent/Relations/HasManyThrough.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index b0b4b1fdebe1..78ac9018bdd0 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -600,6 +600,24 @@ public function chunkById($count, callable $callback, $column = null, $alias = n return $this->prepareQueryBuilder()->chunkById($count, $callback, $column, $alias); } + /** + * Chunk the results of a query by comparing IDs in descending order. + * + * @param int $count + * @param callable $callback + * @param string|null $column + * @param string|null $alias + * @return bool + */ + public function chunkByIdDesc($count, callable $callback, $column = null, $alias = null) + { + $column ??= $this->getRelated()->getQualifiedKeyName(); + + $alias ??= $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->chunkByIdDesc($count, $callback, $column, $alias); + } + /** * Execute a callback over each item while chunking by ID. * From 4eadc6c93f879916d7614dd5a62831d32834c639 Mon Sep 17 00:00:00 2001 From: driesvints Date: Wed, 10 Apr 2024 14:47:03 +0000 Subject: [PATCH 216/325] Update version to v10.48.6 --- 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 9649f34af55b..1aa5d18bb8ac 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.5'; + const VERSION = '10.48.6'; /** * The base path for the Laravel installation. From ae6dc85bf7697e0dba225704800b2140dc3971d0 Mon Sep 17 00:00:00 2001 From: driesvints Date: Wed, 10 Apr 2024 14:49:13 +0000 Subject: [PATCH 217/325] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c52d6040c23e..03bbdc08f3f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.5...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.6...10.x) + +## [v10.48.6](https://github.com/laravel/framework/compare/v10.48.5...v10.48.6) - 2024-04-10 + +* [10.x] Added eachById and chunkByIdDesc to BelongsToMany by [@lonnylot](https://github.com/lonnylot) in https://github.com/laravel/framework/pull/50991 ## [v10.48.5](https://github.com/laravel/framework/compare/v10.48.4...v10.48.5) - 2024-04-09 From 95ef230339b15321493a08327f250c0760c95376 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 10 Apr 2024 09:54:00 -0500 Subject: [PATCH 218/325] fix more query builder methods --- .../Eloquent/Relations/BelongsToMany.php | 23 +++++++++++++++++++ .../Eloquent/Relations/HasManyThrough.php | 17 ++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 73152d7e5879..0f93ee7e390c 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -1115,6 +1115,29 @@ public function lazyById($chunkSize = 1000, $column = null, $alias = null) }); } + /** + * Query lazily, by chunking the results of a query by comparing IDs in descending order. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + */ + public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) + { + $column ??= $this->getRelated()->qualifyColumn( + $this->getRelatedKeyName() + ); + + $alias ??= $this->getRelatedKeyName(); + + return $this->prepareQueryBuilder()->lazyByIdDesc($chunkSize, $column, $alias)->map(function ($model) { + $this->hydratePivotRelation([$model]); + + return $model; + }); + } + /** * Get a lazy collection for the given query. * diff --git a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php index 78ac9018bdd0..20c34749efba 100644 --- a/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasManyThrough.php @@ -692,6 +692,23 @@ public function lazyById($chunkSize = 1000, $column = null, $alias = null) return $this->prepareQueryBuilder()->lazyById($chunkSize, $column, $alias); } + /** + * Query lazily, by chunking the results of a query by comparing IDs in descending order. + * + * @param int $chunkSize + * @param string|null $column + * @param string|null $alias + * @return \Illuminate\Support\LazyCollection + */ + public function lazyByIdDesc($chunkSize = 1000, $column = null, $alias = null) + { + $column ??= $this->getRelated()->getQualifiedKeyName(); + + $alias ??= $this->getRelated()->getKeyName(); + + return $this->prepareQueryBuilder()->lazyByIdDesc($chunkSize, $column, $alias); + } + /** * Prepare the query builder for query execution. * From 118c686992f4b90d4da6deaf0901315c337bbaf9 Mon Sep 17 00:00:00 2001 From: driesvints Date: Wed, 10 Apr 2024 14:57:20 +0000 Subject: [PATCH 219/325] Update version to v10.48.7 --- 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 1aa5d18bb8ac..f8db2b718375 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.6'; + const VERSION = '10.48.7'; /** * The base path for the Laravel installation. From 1ee54e92adbefa09e4d61942ae70296be0e9c27f Mon Sep 17 00:00:00 2001 From: driesvints Date: Wed, 10 Apr 2024 15:02:21 +0000 Subject: [PATCH 220/325] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03bbdc08f3f8..b2b07eca42b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.6...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.7...10.x) + +## [v10.48.7](https://github.com/laravel/framework/compare/v10.48.6...v10.48.7) - 2024-04-10 + +* Fix more query builder methods by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/commit/95ef230339b15321493a08327f250c0760c95376 ## [v10.48.6](https://github.com/laravel/framework/compare/v10.48.5...v10.48.6) - 2024-04-10 From 2c4746a90864ab11ba407375d156b3487562f5ab Mon Sep 17 00:00:00 2001 From: Choraimy Kroonstuiver <3661474+axlon@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:31:05 +0200 Subject: [PATCH 221/325] Fix error when using `orderByRaw()` in query before using `cursorPaginate()` (#51023) --- src/Illuminate/Pagination/AbstractCursorPaginator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Pagination/AbstractCursorPaginator.php b/src/Illuminate/Pagination/AbstractCursorPaginator.php index e6700789c176..fa3070c24521 100644 --- a/src/Illuminate/Pagination/AbstractCursorPaginator.php +++ b/src/Illuminate/Pagination/AbstractCursorPaginator.php @@ -205,6 +205,7 @@ public function getCursorForItem($item, $isNext = true) public function getParametersForItem($item) { return collect($this->parameters) + ->filter() ->flip() ->map(function ($_, $parameterName) use ($item) { if ($item instanceof JsonResource) { From 4ee9b5385afd9e24202c76df8894cb923aac6044 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 11 Apr 2024 16:31:21 +0200 Subject: [PATCH 222/325] Fix flaky test (#51022) --- tests/Database/QueryDurationThresholdTest.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/tests/Database/QueryDurationThresholdTest.php b/tests/Database/QueryDurationThresholdTest.php index 06fb996c974a..2e47ecca1622 100644 --- a/tests/Database/QueryDurationThresholdTest.php +++ b/tests/Database/QueryDurationThresholdTest.php @@ -6,11 +6,17 @@ use Illuminate\Database\Connection; use Illuminate\Events\Dispatcher; use Illuminate\Support\Arr; +use Illuminate\Support\Carbon; use PDO; use PHPUnit\Framework\TestCase; class QueryDurationThresholdTest extends TestCase { + /** + * @var \Illuminate\Support\Carbon + */ + protected $now; + public function testItCanHandleReachingADurationThresholdInTheDb() { $connection = new Connection(new PDO('sqlite::memory:')); @@ -46,10 +52,12 @@ public function testItIsOnlyCalledOnce() public function testItIsOnlyCalledOnceWhenGivenDateTime() { + Carbon::setTestNow($this->now = Carbon::create(2017, 6, 27, 13, 14, 15, 'UTC')); + $connection = new Connection(new PDO('sqlite::memory:')); $connection->setEventDispatcher(new Dispatcher()); $called = 0; - $connection->whenQueryingForLongerThan(now()->addMilliseconds(1), function () use (&$called) { + $connection->whenQueryingForLongerThan($this->now->addMilliseconds(1), function () use (&$called) { $called++; }); @@ -58,6 +66,8 @@ public function testItIsOnlyCalledOnceWhenGivenDateTime() $connection->logQuery('xxxx', [], 1); $this->assertSame(1, $called); + + Carbon::setTestNow(null); } public function testItCanSpecifyMultipleHandlersWithTheSameIntervals() From c9cc18001e0dd14638320a4a770bbc2486fc9eef Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 11 Apr 2024 16:34:08 +0200 Subject: [PATCH 223/325] wip --- tests/Database/QueryDurationThresholdTest.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tests/Database/QueryDurationThresholdTest.php b/tests/Database/QueryDurationThresholdTest.php index 2e47ecca1622..ddcba33a3e90 100644 --- a/tests/Database/QueryDurationThresholdTest.php +++ b/tests/Database/QueryDurationThresholdTest.php @@ -17,6 +17,13 @@ class QueryDurationThresholdTest extends TestCase */ protected $now; + protected function tearDown(): void + { + Carbon::setTestNow(null); + + parent::tearDown(); + } + public function testItCanHandleReachingADurationThresholdInTheDb() { $connection = new Connection(new PDO('sqlite::memory:')); @@ -66,8 +73,6 @@ public function testItIsOnlyCalledOnceWhenGivenDateTime() $connection->logQuery('xxxx', [], 1); $this->assertSame(1, $called); - - Carbon::setTestNow(null); } public function testItCanSpecifyMultipleHandlersWithTheSameIntervals() From 192f909a8b2a702e9e2d9702b836bf6d51e8728b Mon Sep 17 00:00:00 2001 From: Saad <25284407+saadsidqui@users.noreply.github.com> Date: Mon, 15 Apr 2024 14:56:27 -0400 Subject: [PATCH 224/325] - Illuminate\Database\Query::whereIntegerInRaw() now properly handles BackedEnums (#49787) - Illuminate\Database\Eloquent\Relations\BelongsTo::getForeignKeyFrom() handles BackedEnums properly See #49735 Co-authored-by: Saad <25284407+sidquisaad@users.noreply.github.com> --- .../Database/Eloquent/Relations/BelongsTo.php | 5 ++- src/Illuminate/Database/Query/Builder.php | 2 +- .../DatabaseEloquentBelongsToTest.php | 40 ++++++++++++++++++- tests/Database/DatabaseQueryBuilderTest.php | 10 +++-- tests/Database/Fixtures/Enums/Bar.php | 8 ++++ 5 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 tests/Database/Fixtures/Enums/Bar.php diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php b/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php index 8bde76a0c388..112a0edba022 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsTo.php @@ -2,6 +2,7 @@ namespace Illuminate\Database\Eloquent\Relations; +use BackedEnum; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; @@ -375,7 +376,9 @@ protected function getRelatedKeyFrom(Model $model) */ protected function getForeignKeyFrom(Model $model) { - return $model->{$this->foreignKey}; + $foreignKey = $model->{$this->foreignKey}; + + return $foreignKey instanceof BackedEnum ? $foreignKey->value : $foreignKey; } /** diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 6632aa3bf79c..e47a8942993a 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -1207,7 +1207,7 @@ public function whereIntegerInRaw($column, $values, $boolean = 'and', $not = fal $values = Arr::flatten($values); foreach ($values as &$value) { - $value = (int) $value; + $value = (int) ($value instanceof BackedEnum ? $value->value : $value); } $this->wheres[] = compact('type', 'column', 'values', 'boolean'); diff --git a/tests/Database/DatabaseEloquentBelongsToTest.php b/tests/Database/DatabaseEloquentBelongsToTest.php index 0c392e704412..1ace437a4346 100755 --- a/tests/Database/DatabaseEloquentBelongsToTest.php +++ b/tests/Database/DatabaseEloquentBelongsToTest.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Tests\Database\Fixtures\Enums\Bar; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -85,6 +86,16 @@ public function testIdsInEagerConstraintsCanBeZero() $relation->addEagerConstraints($models); } + public function testIdsInEagerConstraintsCanBeBackedEnum() + { + $relation = $this->getRelation(); + $relation->getRelated()->shouldReceive('getKeyName')->andReturn('id'); + $relation->getRelated()->shouldReceive('getKeyType')->andReturn('int'); + $relation->getQuery()->shouldReceive('whereIntegerInRaw')->once()->with('relation.id', [5, 'foreign.value']); + $models = [new EloquentBelongsToModelStub, new EloquentBelongsToModelStubWithBackedEnumCast]; + $relation->addEagerConstraints($models); + } + public function testRelationIsProperlyInitialized() { $relation = $this->getRelation(); @@ -119,6 +130,15 @@ public function __toString() } }; + $result4 = new class extends Model + { + protected $casts = [ + 'id' => Bar::class, + ]; + + protected $attributes = ['id' => 5]; + }; + $model1 = new EloquentBelongsToModelStub; $model1->foreign_key = 1; $model2 = new EloquentBelongsToModelStub; @@ -131,11 +151,18 @@ public function __toString() return '3'; } }; - $models = $relation->match([$model1, $model2, $model3], new Collection([$result1, $result2, $result3]), 'foo'); + $model4 = new EloquentBelongsToModelStub; + $model4->foreign_key = 5; + $models = $relation->match( + [$model1, $model2, $model3, $model4], + new Collection([$result1, $result2, $result3, $result4]), + 'foo' + ); $this->assertEquals(1, $models[0]->foo->getAttribute('id')); $this->assertEquals(2, $models[1]->foo->getAttribute('id')); $this->assertSame('3', (string) $models[2]->foo->getAttribute('id')); + $this->assertEquals(5, $models[3]->foo->getAttribute('id')->value); } public function testAssociateMethodSetsForeignKeyOnModel() @@ -403,3 +430,14 @@ class MissingEloquentBelongsToModelStub extends Model { public $foreign_key; } + +class EloquentBelongsToModelStubWithBackedEnumCast extends Model +{ + protected $casts = [ + 'foreign_key' => Bar::class, + ]; + + public $attributes = [ + 'foreign_key' => 5, + ]; +} diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 40500d9ed514..1743b8966d05 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -23,6 +23,7 @@ use Illuminate\Pagination\Cursor; use Illuminate\Pagination\CursorPaginator; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Tests\Database\Fixtures\Enums\Bar; use InvalidArgumentException; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -1039,8 +1040,10 @@ public function testEmptyWhereNotIns() public function testWhereIntegerInRaw() { $builder = $this->getBuilder(); - $builder->select('*')->from('users')->whereIntegerInRaw('id', ['1a', 2]); - $this->assertSame('select * from "users" where "id" in (1, 2)', $builder->toSql()); + $builder->select('*')->from('users')->whereIntegerInRaw('id', [ + '1a', 2, Bar::FOO, + ]); + $this->assertSame('select * from "users" where "id" in (1, 2, 5)', $builder->toSql()); $this->assertEquals([], $builder->getBindings()); $builder = $this->getBuilder(); @@ -1048,8 +1051,9 @@ public function testWhereIntegerInRaw() ['id' => '1a'], ['id' => 2], ['any' => '3'], + ['id' => Bar::FOO], ]); - $this->assertSame('select * from "users" where "id" in (1, 2, 3)', $builder->toSql()); + $this->assertSame('select * from "users" where "id" in (1, 2, 3, 5)', $builder->toSql()); $this->assertEquals([], $builder->getBindings()); } diff --git a/tests/Database/Fixtures/Enums/Bar.php b/tests/Database/Fixtures/Enums/Bar.php new file mode 100644 index 000000000000..bc019a9be02c --- /dev/null +++ b/tests/Database/Fixtures/Enums/Bar.php @@ -0,0 +1,8 @@ + Date: Tue, 16 Apr 2024 14:26:04 +0000 Subject: [PATCH 225/325] Update version to v10.48.8 --- 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 f8db2b718375..d62f67de5972 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.7'; + const VERSION = '10.48.8'; /** * The base path for the Laravel installation. From 67d4d0dd8061227ef21a3ed7a0ad57c18d51861b Mon Sep 17 00:00:00 2001 From: Thijs van den Anker Date: Tue, 16 Apr 2024 20:51:25 +0200 Subject: [PATCH 226/325] [10.x] Binding order is incorrect when using cursor paginate with multiple unions with a where (#50884) * Add failing test for binding order * Add failing test for bindings with multiple order clauses * Fix binding order for cursor paginate with multiple unions * Use setBindings to clear the union bindings * Add bindings to nested union builders * Only reset union bindings if unions is set * Fix getting union builders for Eloquent queries * Remove duplicate bindings * Passthrough getRawBindings to support cursor pagination logic * Give each builder its own name to make the code easier to trace * Add test for multi union, multi where binding order problem * wip Signed-off-by: Mior Muhammad Zaki * Add docblock * Use FQN in the docblock Co-authored-by: Mior Muhammad Zaki * Use FQN in the docblock Co-authored-by: Mior Muhammad Zaki * formatting * formatting --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: Mior Muhammad Zaki Co-authored-by: Taylor Otwell --- .../Database/Concerns/BuildsQueries.php | 22 +++-- src/Illuminate/Database/Eloquent/Builder.php | 13 +++ src/Illuminate/Database/Query/Builder.php | 12 +++ tests/Database/DatabaseQueryBuilderTest.php | 99 +++++++++++++++++++ .../Database/EloquentCursorPaginateTest.php | 28 ++++++ 5 files changed, 166 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Database/Concerns/BuildsQueries.php b/src/Illuminate/Database/Concerns/BuildsQueries.php index 0d45b0a6f6fc..b2f973afb18e 100644 --- a/src/Illuminate/Database/Concerns/BuildsQueries.php +++ b/src/Illuminate/Database/Concerns/BuildsQueries.php @@ -379,8 +379,11 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = $orders = $this->ensureOrderForCursorPagination(! is_null($cursor) && $cursor->pointsToPreviousItems()); if (! is_null($cursor)) { + // Reset the union bindings so we can add the cursor where in the correct position... + $this->setBindings([], 'union'); + $addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) { - $unionBuilders = isset($builder->unions) ? collect($builder->unions)->pluck('query') : collect(); + $unionBuilders = $builder->getUnionBuilders(); if (! is_null($previousColumn)) { $originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $previousColumn); @@ -402,25 +405,27 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = }); } - $builder->where(function (self $builder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) { + $builder->where(function (self $secondBuilder) use ($addCursorConditions, $cursor, $orders, $i, $unionBuilders) { ['column' => $column, 'direction' => $direction] = $orders[$i]; $originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $column); - $builder->where( + $secondBuilder->where( Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn, $direction === 'asc' ? '>' : '<', $cursor->parameter($column) ); if ($i < $orders->count() - 1) { - $builder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) { - $addCursorConditions($builder, $column, $i + 1); + $secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $i) { + $addCursorConditions($thirdBuilder, $column, $i + 1); }); } $unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { - $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { + $unionWheres = $unionBuilder->getRawBindings()['where']; + + $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $unionWheres) { $unionBuilder->where( $this->getOriginalColumnNameForCursorPagination($this, $column), $direction === 'asc' ? '>' : '<', @@ -428,11 +433,12 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = ); if ($i < $orders->count() - 1) { - $unionBuilder->orWhere(function (self $builder) use ($addCursorConditions, $column, $i) { - $addCursorConditions($builder, $column, $i + 1); + $unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $i) { + $addCursorConditions($fourthBuilder, $column, $i + 1); }); } + $this->addBinding($unionWheres, 'union'); $this->addBinding($unionBuilder->getRawBindings()['where'], 'union'); }); }); diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 185781580684..8ed9229e0894 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -108,6 +108,7 @@ class Builder implements BuilderContract 'getbindings', 'getconnection', 'getgrammar', + 'getrawbindings', 'implode', 'insert', 'insertgetid', @@ -1727,6 +1728,18 @@ public function withSavepointIfNeeded(Closure $scope): mixed : $scope(); } + /** + * Get the Eloquent builder instances that are used in the union of the query. + * + * @return \Illuminate\Support\Collection + */ + protected function getUnionBuilders() + { + return isset($this->query->unions) + ? collect($this->query->unions)->pluck('query') + : collect(); + } + /** * Get the underlying query builder instance. * diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index e47a8942993a..210d8cbb25a1 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -3815,6 +3815,18 @@ public function raw($value) return $this->connection->raw($value); } + /** + * Get the query builder instances that are used in the union of the query. + * + * @return \Illuminate\Support\Collection + */ + protected function getUnionBuilders() + { + return isset($this->unions) + ? collect($this->unions)->pluck('query') + : collect(); + } + /** * Get the current query value bindings in a flattened array. * diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 1743b8966d05..90125356bdd8 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -5297,6 +5297,105 @@ public function testCursorPaginateWithUnionWheres() ]), $result); } + public function testCursorPaginateWithMultipleUnionsAndMultipleWheres() + { + $ts = now()->toDateTimeString(); + + $perPage = 16; + $columns = ['test']; + $cursorName = 'cursor-name'; + $cursor = new Cursor(['created_at' => $ts]); + $builder = $this->getMockQueryBuilder(); + $builder->select('id', 'start_time as created_at')->selectRaw("'video' as type")->from('videos'); + $builder->union($this->getBuilder()->select('id', 'created_at')->selectRaw("'news' as type")->from('news')->where('extra', 'first')); + $builder->union($this->getBuilder()->select('id', 'created_at')->selectRaw("'podcast' as type")->from('podcasts')->where('extra', 'second')); + $builder->orderBy('created_at'); + + $builder->shouldReceive('newQuery')->andReturnUsing(function () use ($builder) { + return new Builder($builder->connection, $builder->grammar, $builder->processor); + }); + + $path = 'http://foo.bar?cursor='.$cursor->encode(); + + $results = collect([ + ['id' => 1, 'created_at' => now(), 'type' => 'video'], + ['id' => 2, 'created_at' => now(), 'type' => 'news'], + ['id' => 3, 'created_at' => now(), 'type' => 'podcasts'], + ]); + + $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { + $this->assertEquals( + '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where "extra" = ? and ("start_time" > ?)) union (select "id", "created_at", \'podcast\' as type from "podcasts" where "extra" = ? and ("start_time" > ?)) order by "created_at" asc limit 17', + $builder->toSql()); + $this->assertEquals([$ts], $builder->bindings['where']); + $this->assertEquals(['first', $ts, 'second', $ts], $builder->bindings['union']); + + return $results; + }); + + Paginator::currentPathResolver(function () use ($path) { + return $path; + }); + + $result = $builder->cursorPaginate($perPage, $columns, $cursorName, $cursor); + + $this->assertEquals(new CursorPaginator($results, $perPage, $cursor, [ + 'path' => $path, + 'cursorName' => $cursorName, + 'parameters' => ['created_at'], + ]), $result); + } + + public function testCursorPaginateWithUnionMultipleWheresMultipleOrders() + { + $ts = now()->toDateTimeString(); + + $perPage = 16; + $columns = ['id', 'created_at', 'type']; + $cursorName = 'cursor-name'; + $cursor = new Cursor(['id' => 1, 'created_at' => $ts, 'type' => 'news']); + $builder = $this->getMockQueryBuilder(); + $builder->select('id', 'start_time as created_at', 'type')->from('videos')->where('extra', 'first'); + $builder->union($this->getBuilder()->select('id', 'created_at', 'type')->from('news')->where('extra', 'second')); + $builder->union($this->getBuilder()->select('id', 'created_at', 'type')->from('podcasts')->where('extra', 'third')); + $builder->orderBy('id')->orderByDesc('created_at')->orderBy('type'); + + $builder->shouldReceive('newQuery')->andReturnUsing(function () use ($builder) { + return new Builder($builder->connection, $builder->grammar, $builder->processor); + }); + + $path = 'http://foo.bar?cursor='.$cursor->encode(); + + $results = collect([ + ['id' => 1, 'created_at' => now()->addDay(), 'type' => 'video'], + ['id' => 1, 'created_at' => now(), 'type' => 'news'], + ['id' => 1, 'created_at' => now(), 'type' => 'podcast'], + ['id' => 2, 'created_at' => now(), 'type' => 'podcast'], + ]); + + $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { + $this->assertEquals( + '(select "id", "start_time" as "created_at", "type" from "videos" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) union (select "id", "created_at", "type" from "news" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) union (select "id", "created_at", "type" from "podcasts" where "extra" = ? and ("id" > ? or ("id" = ? and ("start_time" < ? or ("start_time" = ? and ("type" > ?)))))) order by "id" asc, "created_at" desc, "type" asc limit 17', + $builder->toSql()); + $this->assertEquals(['first', 1, 1, $ts, $ts, 'news'], $builder->bindings['where']); + $this->assertEquals(['second', 1, 1, $ts, $ts, 'news', 'third', 1, 1, $ts, $ts, 'news'], $builder->bindings ['union']); + + return $results; + }); + + Paginator::currentPathResolver(function () use ($path) { + return $path; + }); + + $result = $builder->cursorPaginate($perPage, $columns, $cursorName, $cursor); + + $this->assertEquals(new CursorPaginator($results, $perPage, $cursor, [ + 'path' => $path, + 'cursorName' => $cursorName, + 'parameters' => ['id', 'created_at', 'type'], + ]), $result); + } + public function testCursorPaginateWithUnionWheresWithRawOrderExpression() { $ts = now()->toDateTimeString(); diff --git a/tests/Integration/Database/EloquentCursorPaginateTest.php b/tests/Integration/Database/EloquentCursorPaginateTest.php index 01fd4b49e8b8..02034c485632 100644 --- a/tests/Integration/Database/EloquentCursorPaginateTest.php +++ b/tests/Integration/Database/EloquentCursorPaginateTest.php @@ -167,6 +167,34 @@ public function testPaginationWithMultipleWhereClauses() ); } + public function testPaginationWithMultipleUnionAndMultipleWhereClauses() + { + TestPost::create(['title' => 'Post A', 'user_id' => 100]); + TestPost::create(['title' => 'Post B', 'user_id' => 101]); + + $table1 = TestPost::select(['id', 'title', 'user_id'])->where('user_id', 100); + $table2 = TestPost::select(['id', 'title', 'user_id'])->where('user_id', 101); + $table3 = TestPost::select(['id', 'title', 'user_id'])->where('user_id', 101); + + $columns = ['id']; + $cursorName = 'cursor-name'; + $cursor = new Cursor(['id' => 1]); + + $result = $table1->toBase() + ->union($table2->toBase()) + ->union($table3->toBase()) + ->orderBy('id', 'asc') + ->cursorPaginate(1, $columns, $cursorName, $cursor); + + $this->assertSame(['id'], $result->getOptions()['parameters']); + + $postB = $table2->where('id', '>', 1)->first(); + $this->assertEquals('Post B', $postB->title, 'Expect `Post B` is the result of the second query'); + + $this->assertCount(1, $result->items(), 'Expect cursor paginated query should have 1 result'); + $this->assertEquals('Post B', current($result->items())->title, 'Expect the paginated query would return `Post B`'); + } + public function testPaginationWithAliasedOrderBy() { for ($i = 1; $i <= 6; $i++) { From 877ebcab5fb0d97b8e8eb7c213dd549647e071ad Mon Sep 17 00:00:00 2001 From: Thijs van den Anker Date: Tue, 16 Apr 2024 21:48:48 +0200 Subject: [PATCH 227/325] [10.x] Fix cursor paginate with union and column alias (#50882) * Cursor paginate uses incorrect column name for where on union * Set the right column names for the cursor on the unions * Add dedicated test to test correct column alias on union cursor paginate * Add eloquent test for cursor pagination with union and multiple aliases * Use correct column name in cursor where clause --- .../Database/Concerns/BuildsQueries.php | 21 +++---- tests/Database/DatabaseQueryBuilderTest.php | 59 +++++++++++++++++-- .../Database/EloquentCursorPaginateTest.php | 27 +++++++++ 3 files changed, 92 insertions(+), 15 deletions(-) diff --git a/src/Illuminate/Database/Concerns/BuildsQueries.php b/src/Illuminate/Database/Concerns/BuildsQueries.php index b2f973afb18e..c41a58b32687 100644 --- a/src/Illuminate/Database/Concerns/BuildsQueries.php +++ b/src/Illuminate/Database/Concerns/BuildsQueries.php @@ -382,11 +382,11 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = // Reset the union bindings so we can add the cursor where in the correct position... $this->setBindings([], 'union'); - $addCursorConditions = function (self $builder, $previousColumn, $i) use (&$addCursorConditions, $cursor, $orders) { + $addCursorConditions = function (self $builder, $previousColumn, $originalColumn, $i) use (&$addCursorConditions, $cursor, $orders) { $unionBuilders = $builder->getUnionBuilders(); if (! is_null($previousColumn)) { - $originalColumn = $this->getOriginalColumnNameForCursorPagination($this, $previousColumn); + $originalColumn ??= $this->getOriginalColumnNameForCursorPagination($this, $previousColumn); $builder->where( Str::contains($originalColumn, ['(', ')']) ? new Expression($originalColumn) : $originalColumn, @@ -396,7 +396,7 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = $unionBuilders->each(function ($unionBuilder) use ($previousColumn, $cursor) { $unionBuilder->where( - $this->getOriginalColumnNameForCursorPagination($this, $previousColumn), + $this->getOriginalColumnNameForCursorPagination($unionBuilder, $previousColumn), '=', $cursor->parameter($previousColumn) ); @@ -417,24 +417,25 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = ); if ($i < $orders->count() - 1) { - $secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $i) { - $addCursorConditions($thirdBuilder, $column, $i + 1); + $secondBuilder->orWhere(function (self $thirdBuilder) use ($addCursorConditions, $column, $originalColumn, $i) { + $addCursorConditions($thirdBuilder, $column, $originalColumn, $i + 1); }); } $unionBuilders->each(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions) { $unionWheres = $unionBuilder->getRawBindings()['where']; - $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $unionWheres) { + $originalColumn = $this->getOriginalColumnNameForCursorPagination($unionBuilder, $column); + $unionBuilder->where(function ($unionBuilder) use ($column, $direction, $cursor, $i, $orders, $addCursorConditions, $originalColumn, $unionWheres) { $unionBuilder->where( - $this->getOriginalColumnNameForCursorPagination($this, $column), + $originalColumn, $direction === 'asc' ? '>' : '<', $cursor->parameter($column) ); if ($i < $orders->count() - 1) { - $unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $i) { - $addCursorConditions($fourthBuilder, $column, $i + 1); + $unionBuilder->orWhere(function (self $fourthBuilder) use ($addCursorConditions, $column, $originalColumn, $i) { + $addCursorConditions($fourthBuilder, $column, $originalColumn, $i + 1); }); } @@ -445,7 +446,7 @@ protected function paginateUsingCursor($perPage, $columns = ['*'], $cursorName = }); }; - $addCursorConditions($this, null, 0); + $addCursorConditions($this, null, null, 0); } $this->limit($perPage + 1); diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 90125356bdd8..39f49f45d53c 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -5276,7 +5276,7 @@ public function testCursorPaginateWithUnionWheres() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where ("start_time" > ?)) order by "created_at" asc limit 17', + '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where ("created_at" > ?)) order by "created_at" asc limit 17', $builder->toSql()); $this->assertEquals([$ts], $builder->bindings['where']); $this->assertEquals([$ts], $builder->bindings['union']); @@ -5325,7 +5325,7 @@ public function testCursorPaginateWithMultipleUnionsAndMultipleWheres() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where "extra" = ? and ("start_time" > ?)) union (select "id", "created_at", \'podcast\' as type from "podcasts" where "extra" = ? and ("start_time" > ?)) order by "created_at" asc limit 17', + '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where "extra" = ? and ("created_at" > ?)) union (select "id", "created_at", \'podcast\' as type from "podcasts" where "extra" = ? and ("created_at" > ?)) order by "created_at" asc limit 17', $builder->toSql()); $this->assertEquals([$ts], $builder->bindings['where']); $this->assertEquals(['first', $ts, 'second', $ts], $builder->bindings['union']); @@ -5422,7 +5422,7 @@ public function testCursorPaginateWithUnionWheresWithRawOrderExpression() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(select "id", "is_published", "start_time" as "created_at", \'video\' as type from "videos" where "is_published" = ? and ("start_time" > ?)) union (select "id", "is_published", "created_at", \'news\' as type from "news" where "is_published" = ? and ("start_time" > ?)) order by case when (id = 3 and type="news" then 0 else 1 end), "created_at" asc limit 17', + '(select "id", "is_published", "start_time" as "created_at", \'video\' as type from "videos" where "is_published" = ? and ("start_time" > ?)) union (select "id", "is_published", "created_at", \'news\' as type from "news" where "is_published" = ? and ("created_at" > ?)) order by case when (id = 3 and type="news" then 0 else 1 end), "created_at" asc limit 17', $builder->toSql()); $this->assertEquals([true, $ts], $builder->bindings['where']); $this->assertEquals([true, $ts], $builder->bindings['union']); @@ -5469,7 +5469,7 @@ public function testCursorPaginateWithUnionWheresReverseOrder() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" < ?)) union (select "id", "created_at", \'news\' as type from "news" where ("start_time" < ?)) order by "created_at" desc limit 17', + '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" < ?)) union (select "id", "created_at", \'news\' as type from "news" where ("created_at" < ?)) order by "created_at" desc limit 17', $builder->toSql()); $this->assertEquals([$ts], $builder->bindings['where']); $this->assertEquals([$ts], $builder->bindings['union']); @@ -5516,7 +5516,7 @@ public function testCursorPaginateWithUnionWheresMultipleOrders() $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { $this->assertEquals( - '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" < ? or ("start_time" = ? and ("id" > ?)))) union (select "id", "created_at", \'news\' as type from "news" where ("start_time" < ? or ("start_time" = ? and ("id" > ?)))) order by "created_at" desc, "id" asc limit 17', + '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" < ? or ("start_time" = ? and ("id" > ?)))) union (select "id", "created_at", \'news\' as type from "news" where ("created_at" < ? or ("created_at" = ? and ("id" > ?)))) order by "created_at" desc, "id" asc limit 17', $builder->toSql()); $this->assertEquals([$ts, $ts, 1], $builder->bindings['where']); $this->assertEquals([$ts, $ts, 1], $builder->bindings['union']); @@ -5537,6 +5537,55 @@ public function testCursorPaginateWithUnionWheresMultipleOrders() ]), $result); } + public function testCursorPaginateWithUnionWheresAndAliassedOrderColumns() + { + $ts = now()->toDateTimeString(); + + $perPage = 16; + $columns = ['test']; + $cursorName = 'cursor-name'; + $cursor = new Cursor(['created_at' => $ts]); + $builder = $this->getMockQueryBuilder(); + $builder->select('id', 'start_time as created_at')->selectRaw("'video' as type")->from('videos'); + $builder->union($this->getBuilder()->select('id', 'created_at')->selectRaw("'news' as type")->from('news')); + $builder->union($this->getBuilder()->select('id', 'init_at as created_at')->selectRaw("'podcast' as type")->from('podcasts')); + $builder->orderBy('created_at'); + + $builder->shouldReceive('newQuery')->andReturnUsing(function () use ($builder) { + return new Builder($builder->connection, $builder->grammar, $builder->processor); + }); + + $path = 'http://foo.bar?cursor='.$cursor->encode(); + + $results = collect([ + ['id' => 1, 'created_at' => now(), 'type' => 'video'], + ['id' => 2, 'created_at' => now(), 'type' => 'news'], + ['id' => 3, 'created_at' => now(), 'type' => 'podcast'], + ]); + + $builder->shouldReceive('get')->once()->andReturnUsing(function () use ($builder, $results, $ts) { + $this->assertEquals( + '(select "id", "start_time" as "created_at", \'video\' as type from "videos" where ("start_time" > ?)) union (select "id", "created_at", \'news\' as type from "news" where ("created_at" > ?)) union (select "id", "init_at" as "created_at", \'podcast\' as type from "podcasts" where ("init_at" > ?)) order by "created_at" asc limit 17', + $builder->toSql()); + $this->assertEquals([$ts], $builder->bindings['where']); + $this->assertEquals([$ts, $ts], $builder->bindings['union']); + + return $results; + }); + + Paginator::currentPathResolver(function () use ($path) { + return $path; + }); + + $result = $builder->cursorPaginate($perPage, $columns, $cursorName, $cursor); + + $this->assertEquals(new CursorPaginator($results, $perPage, $cursor, [ + 'path' => $path, + 'cursorName' => $cursorName, + 'parameters' => ['created_at'], + ]), $result); + } + public function testWhereExpression() { $builder = $this->getBuilder(); diff --git a/tests/Integration/Database/EloquentCursorPaginateTest.php b/tests/Integration/Database/EloquentCursorPaginateTest.php index 02034c485632..54480f90bf76 100644 --- a/tests/Integration/Database/EloquentCursorPaginateTest.php +++ b/tests/Integration/Database/EloquentCursorPaginateTest.php @@ -21,6 +21,7 @@ protected function afterRefreshingDatabase() Schema::create('test_users', function ($table) { $table->increments('id'); + $table->string('name')->nullable(); $table->timestamps(); }); } @@ -195,6 +196,32 @@ public function testPaginationWithMultipleUnionAndMultipleWhereClauses() $this->assertEquals('Post B', current($result->items())->title, 'Expect the paginated query would return `Post B`'); } + public function testPaginationWithMultipleAliases() + { + TestUser::create(['name' => 'A (user)']); + TestUser::create(['name' => 'C (user)']); + + TestPost::create(['title' => 'B (post)']); + TestPost::create(['title' => 'D (post)']); + + $table1 = TestPost::select(['title as alias']); + $table2 = TestUser::select(['name as alias']); + + $columns = ['alias']; + $cursorName = 'cursor-name'; + $cursor = new Cursor(['alias' => 'A (user)']); + + $result = $table1->toBase() + ->union($table2->toBase()) + ->orderBy('alias', 'asc') + ->cursorPaginate(1, $columns, $cursorName, $cursor); + + $this->assertSame(['alias'], $result->getOptions()['parameters']); + + $this->assertCount(1, $result->items(), 'Expect cursor paginated query should have 1 result'); + $this->assertEquals('B (post)', current($result->items())->alias, 'Expect the paginated query would return `B (post)`'); + } + public function testPaginationWithAliasedOrderBy() { for ($i = 1; $i <= 6; $i++) { From 25e285e2fe99a6a8f43af493a0be6937de93cd39 Mon Sep 17 00:00:00 2001 From: Aldo Barreras Date: Mon, 22 Apr 2024 09:20:30 -0400 Subject: [PATCH 228/325] [10.x] Address Null Parameter Deprecations in UrlGenerator (#51148) * Replace null values with empty strings for strpos. Replace null values with empty strings for preg_match. * Revert change to UrlGenerator. Change default header get to empty string. --- src/Illuminate/Testing/TestResponse.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index ebf06a7dc1fd..6a583f13b651 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -310,7 +310,7 @@ public function assertHeaderMissing($headerName) public function assertLocation($uri) { PHPUnit::assertEquals( - app('url')->to($uri), app('url')->to($this->headers->get('Location')) + app('url')->to($uri), app('url')->to($this->headers->get('Location', '')) ); return $this; @@ -324,7 +324,7 @@ public function assertLocation($uri) */ public function assertDownload($filename = null) { - $contentDisposition = explode(';', $this->headers->get('content-disposition')); + $contentDisposition = explode(';', $this->headers->get('content-disposition', '')); if (trim($contentDisposition[0]) !== 'attachment') { PHPUnit::fail( From b7516a823d8db2ee14a6159a575738ee0a0a0dd1 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 23 Apr 2024 17:00:46 +0200 Subject: [PATCH 229/325] Update CHANGELOG.md --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b07eca42b2..a18645c73944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.7...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.8...10.x) + +## [v10.48.8](https://github.com/laravel/framework/compare/v10.48.7...v10.48.8) - 2024-04-17 + +* [10.x] Fix error when using `orderByRaw()` in query before using `cursorPaginate()` by @axlon in https://github.com/laravel/framework/pull/51023 +* [10.x] Database layer fixes by @saadsidqui in https://github.com/laravel/framework/pull/49787 ## [v10.48.7](https://github.com/laravel/framework/compare/v10.48.6...v10.48.7) - 2024-04-10 From ad758500b47964d022addf119600a1b1b0230733 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 23 Apr 2024 15:01:33 +0000 Subject: [PATCH 230/325] Update version to v10.48.9 --- 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 d62f67de5972..4bfd6a33e88e 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.8'; + const VERSION = '10.48.9'; /** * The base path for the Laravel installation. From e1467ce7be3f34103eee1c0debea619e6d825dbd Mon Sep 17 00:00:00 2001 From: driesvints Date: Thu, 25 Apr 2024 09:44:21 +0000 Subject: [PATCH 231/325] Update CHANGELOG --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a18645c73944..63f2cbfeda66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.8...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.9...10.x) + +## [v10.48.9](https://github.com/laravel/framework/compare/v10.48.8...v10.48.9) - 2024-04-23 + +* [10.x] Binding order is incorrect when using cursor paginate with multiple unions with a where by [@thijsvdanker](https://github.com/thijsvdanker) in https://github.com/laravel/framework/pull/50884 +* [10.x] Fix cursor paginate with union and column alias by [@thijsvdanker](https://github.com/thijsvdanker) in https://github.com/laravel/framework/pull/50882 +* [10.x] Address Null Parameter Deprecations in UrlGenerator by [@aldobarr](https://github.com/aldobarr) in https://github.com/laravel/framework/pull/51148 ## [v10.48.8](https://github.com/laravel/framework/compare/v10.48.7...v10.48.8) - 2024-04-17 From a43537522094a109c22d8852b6e1d510a55c9313 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 25 Apr 2024 21:25:42 +0200 Subject: [PATCH 232/325] Improve releases flow (#51213) --- .github/workflows/releases.yml | 96 ++++++++++++++++++++------ .github/workflows/update-changelog.yml | 9 --- 2 files changed, 75 insertions(+), 30 deletions(-) delete mode 100644 .github/workflows/update-changelog.yml diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index fe41be64b0f7..359877d89f72 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -7,10 +7,19 @@ on: description: 'Version to release' required: true +permissions: + contents: write + jobs: release: runs-on: ubuntu-latest + name: Release ${{ inputs.version }} + + outputs: + version: ${{ steps.version.outputs.version }} + notes: ${{ steps.cleaned-notes.outputs.release-notes }} + steps: - name: Checkout repository uses: actions/checkout@v4 @@ -21,6 +30,35 @@ jobs: VERSION=${{ inputs.version }} echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT" + - name: Check if branch and version match + id: guard + run: | + MAJOR_VERSION="${NUMERIC_VERSION%%.*}" + BRANCH_MAJOR_VERSION="${BRANCH%%.*}" + + echo "MAJOR_VERSION=$(echo $MAJOR_VERSION)" >> $GITHUB_OUTPUT; + echo "BRANCH_MAJOR_VERSION=$(echo $BRANCH_MAJOR_VERSION)" >> $GITHUB_OUTPUT; + + if [ "$MAJOR_VERSION" != "$BRANCH_MAJOR_VERSION" ]; then + echo "Mismatched versions! Aborting." + VERSION_MISMATCH='true'; + else + echo "Versions match! Proceeding." + VERSION_MISMATCH='false'; + fi + + echo "VERSION_MISMATCH=$(echo $VERSION_MISMATCH)" >> $GITHUB_OUTPUT; + env: + BRANCH: ${{ github.ref_name }} + NUMERIC_VERSION: ${{ steps.version.outputs.version }} + + - name: Fail if branch and release tag do not match + if: ${{ steps.guard.outputs.VERSION_MISMATCH == 'true' }} + uses: actions/github-script@v7 + with: + script: | + core.setFailed('Workflow failed. Release version does not match with selected target branch. Did you select the correct branch?') + - name: Update Application.php version run: sed -i "s/const VERSION = '.*';/const VERSION = '${{ steps.version.outputs.version }}';/g" src/Illuminate/Foundation/Application.php @@ -41,24 +79,40 @@ jobs: bash ./bin/release.sh v${{ steps.version.outputs.version }} script_stop: true - # - name: Generate release notes - # id: notes - # uses: RedCrafter07/release-notes-action@main - # with: - # tag-name: v${{ steps.version.outputs.version }} - # token: ${{ secrets.GITHUB_TOKEN }} - # branch: ${{ github.ref_name }} - - # - name: Cleanup release notes - # run: | - # sed -i '/## What/d' ${{ steps.notes.outputs.release-notes }} - # sed -i '/## New Contributors/,$d' ${{ steps.notes.outputs.release-notes }} - - # - name: Create release - # uses: softprops/action-gh-release@v1 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # tag_name: v${{ steps.version.outputs.version }} - # name: v${{ steps.version.outputs.version }} - # body: ${{ steps.notes.outputs.release-notes }} + - name: Generate release notes + id: generated-notes + uses: RedCrafter07/release-notes-action@main + with: + tag-name: v${{ steps.version.outputs.version }} + token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref_name }} + + - name: Cleanup release notes + id: cleaned-notes + run: | + NOTES="${{ steps.generated-notes.outputs.release-notes }}" + NOTES=$(echo $NOTES | sed '/## What/d') + NOTES=$(echo $NOTES | sed '/## New Contributors/,$d') + echo "release-notes=${NOTES}" >> "$GITHUB_OUTPUT" + + - name: Create release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.version.outputs.version }} + name: v${{ steps.version.outputs.version }} + body: ${{ steps.cleaned-notes.outputs.release-notes }} + target_commitish: ${{ github.ref_name }} + make_latest: 'legacy' + + update-changelog: + needs: release + + name: Update changelog + + uses: laravel/.github/.github/workflows/update-changelog.yml@main + with: + branch: ${{ github.ref_name }} + version: "v${{ needs.release.outputs.version }}" + notes: ${{ needs.release.outputs.notes }} diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml deleted file mode 100644 index 1625bda1002c..000000000000 --- a/.github/workflows/update-changelog.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: update changelog - -on: - release: - types: [released] - -jobs: - update: - uses: laravel/.github/.github/workflows/update-changelog.yml@main From 89462a71233011cd10e554b002ab1c005e5cf979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Sandstr=C3=B6m=20Krisell?= Date: Mon, 29 Apr 2024 08:51:46 +0200 Subject: [PATCH 233/325] Fix typo in signed URL tampering tests (#51238) --- tests/Routing/RoutingUrlGeneratorTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Routing/RoutingUrlGeneratorTest.php b/tests/Routing/RoutingUrlGeneratorTest.php index 2c6079adf36b..c612affaa44b 100755 --- a/tests/Routing/RoutingUrlGeneratorTest.php +++ b/tests/Routing/RoutingUrlGeneratorTest.php @@ -764,7 +764,7 @@ public function testSignedUrl() $this->assertTrue($url->hasValidSignature($request)); - $request = Request::create($url->signedRoute('foo').'?tempered=true'); + $request = Request::create($url->signedRoute('foo').'?tampered=true'); $this->assertFalse($url->hasValidSignature($request)); } @@ -812,7 +812,7 @@ public function testSignedRelativeUrl() $this->assertTrue($url->hasValidSignature($request, false)); - $request = Request::create($url->signedRoute('foo', [], null, false).'?tempered=true'); + $request = Request::create($url->signedRoute('foo', [], null, false).'?tampered=true'); $this->assertFalse($url->hasValidSignature($request, false)); } @@ -891,7 +891,7 @@ public function testSignedUrlWithKeyResolver() $this->assertTrue($url->hasValidSignature($request)); - $request = Request::create($url->signedRoute('foo').'?tempered=true'); + $request = Request::create($url->signedRoute('foo').'?tampered=true'); $this->assertFalse($url->hasValidSignature($request)); From 236ee6df345fe2cac8ce020348ee57e6b8e07e8d Mon Sep 17 00:00:00 2001 From: Julius Kiekbusch Date: Mon, 29 Apr 2024 15:23:17 +0200 Subject: [PATCH 234/325] Add "Server has gone away" to DetectsLostConnection (#51241) --- src/Illuminate/Database/DetectsLostConnections.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index f946f35dc59c..8cb1187a8bb7 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -19,6 +19,7 @@ protected function causedByLostConnection(Throwable $e) return Str::contains($message, [ 'server has gone away', + 'Server has gone away', 'no connection to the server', 'Lost connection', 'is dead or not enabled', From 0bafaa8b4f7dccc2ab3e52fb896b4967352fe95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Dunglas?= Date: Mon, 29 Apr 2024 16:41:28 +0200 Subject: [PATCH 235/325] [10.x] Fix support for the LARAVEL_STORAGE_PATH env var (#51238) (#51243) * [10.x] Fix support for the LARAVEL_STORAGE_PATH env var * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Foundation/Application.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index 4bfd6a33e88e..780e03b81f8f 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -535,6 +535,10 @@ public function storagePath($path = '') return $this->joinPaths($this->storagePath ?: $_ENV['LARAVEL_STORAGE_PATH'], $path); } + if (isset($_SERVER['LARAVEL_STORAGE_PATH'])) { + return $this->joinPaths($this->storagePath ?: $_SERVER['LARAVEL_STORAGE_PATH'], $path); + } + return $this->joinPaths($this->storagePath ?: $this->basePath('storage'), $path); } From 91e2b9e218afa4e5c377510faa11957042831ba3 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 30 Apr 2024 12:52:59 +0000 Subject: [PATCH 236/325] Update version to v10.48.10 --- 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 780e03b81f8f..f419828c6430 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.9'; + const VERSION = '10.48.10'; /** * The base path for the Laravel installation. From 1de31568c504cb284e7ed81fab77ec84601dd509 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Apr 2024 15:11:49 +0200 Subject: [PATCH 237/325] wip --- .github/workflows/releases.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 359877d89f72..7f64247091d9 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -90,10 +90,11 @@ jobs: - name: Cleanup release notes id: cleaned-notes run: | - NOTES="${{ steps.generated-notes.outputs.release-notes }}" - NOTES=$(echo $NOTES | sed '/## What/d') - NOTES=$(echo $NOTES | sed '/## New Contributors/,$d') - echo "release-notes=${NOTES}" >> "$GITHUB_OUTPUT" + RELEASE_NOTES=$(echo $NOTES | sed '/## What/d') + RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## New Contributors/,$d') + echo "release-notes=${RELEASE_NOTES}" >> "$GITHUB_OUTPUT" + env: + NOTES: ${{ steps.generated-notes.outputs.release-notes }} - name: Create release uses: softprops/action-gh-release@v2 From 5ae8fa0d6576a78f8be6003a0a0439f25e40361a Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Apr 2024 15:27:08 +0200 Subject: [PATCH 238/325] wip --- .github/workflows/releases.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 7f64247091d9..39891d4db11c 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -90,9 +90,9 @@ jobs: - name: Cleanup release notes id: cleaned-notes run: | - RELEASE_NOTES=$(echo $NOTES | sed '/## What/d') - RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## New Contributors/,$d') - echo "release-notes=${RELEASE_NOTES}" >> "$GITHUB_OUTPUT" + NOTES=$(echo $NOTES | sed '/## What/d') + NOTES=$(echo $NOTES | sed '/## New Contributors/,$d') + echo "release-notes=${NOTES}" >> "$GITHUB_OUTPUT" env: NOTES: ${{ steps.generated-notes.outputs.release-notes }} From 470e3e8723b4155a00ca743e57780560bad3b0d9 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Apr 2024 15:28:25 +0200 Subject: [PATCH 239/325] update changelog --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63f2cbfeda66..d5d03da62763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.9...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.10...10.x) + +## [v10.48.10](https://github.com/laravel/framework/compare/v10.48.9...v10.48.10) - 2024-04-30 + +* [10.x] Fix typo in signed URL tampering tests by @Krisell in https://github.com/laravel/framework/pull/51238 +* [10.x] Add "Server has gone away" to DetectsLostConnection by @Jubeki in https://github.com/laravel/framework/pull/51241 +* [10.x] Fix support for the LARAVEL_STORAGE_PATH env var (#51238) by @dunglas in https://github.com/laravel/framework/pull/51243 ## [v10.48.9](https://github.com/laravel/framework/compare/v10.48.8...v10.48.9) - 2024-04-23 From 67fbbc49eec22bd80178e2de991d67aea29c6d06 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 30 Apr 2024 15:42:57 +0200 Subject: [PATCH 240/325] wip --- .github/workflows/releases.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 39891d4db11c..8dcfa62b65c0 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -18,7 +18,7 @@ jobs: outputs: version: ${{ steps.version.outputs.version }} - notes: ${{ steps.cleaned-notes.outputs.release-notes }} + notes: ${{ steps.cleaned-notes.outputs.notes }} steps: - name: Checkout repository @@ -90,11 +90,11 @@ jobs: - name: Cleanup release notes id: cleaned-notes run: | - NOTES=$(echo $NOTES | sed '/## What/d') - NOTES=$(echo $NOTES | sed '/## New Contributors/,$d') - echo "release-notes=${NOTES}" >> "$GITHUB_OUTPUT" + RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## What/d') + RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## New Contributors/,$d') + echo "notes=${RELEASE_NOTES}" >> "$GITHUB_OUTPUT" env: - NOTES: ${{ steps.generated-notes.outputs.release-notes }} + RELEASE_NOTES: ${{ steps.generated-notes.outputs.release-notes }} - name: Create release uses: softprops/action-gh-release@v2 @@ -103,7 +103,7 @@ jobs: with: tag_name: v${{ steps.version.outputs.version }} name: v${{ steps.version.outputs.version }} - body: ${{ steps.cleaned-notes.outputs.release-notes }} + body: ${{ steps.cleaned-notes.outputs.notes }} target_commitish: ${{ github.ref_name }} make_latest: 'legacy' From 7e4a539df43923c8b7873766fd8bea3f8deba9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 15:52:32 +0300 Subject: [PATCH 241/325] feat: Add `exists` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 87e47b55226c..3af044ecd184 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -242,4 +242,16 @@ public function getIterator(): Traversable { return new ArrayIterator($this->input); } + + /** + * Determine if the validated inputs contains a given input item key. + * + * @param string|array $key + * @return bool + */ + public function exists($key) + { + return $this->has($key); + } + } From ffd5860698a7c067afa9c6571ad0f7350e51c9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 15:53:06 +0300 Subject: [PATCH 242/325] feat: Add `hasAny` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 3af044ecd184..4e151298c86a 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -254,4 +254,19 @@ public function exists($key) return $this->has($key); } + /** + * Determine if the validated inputs contains any of the given inputs. + * + * @param string|array $keys + * @return bool + */ + public function hasAny($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + $input = $this->all(); + + return Arr::hasAny($input, $keys); + } + } From 888424d78d0dae5fdd56ad176a68382437ecfe64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 15:54:01 +0300 Subject: [PATCH 243/325] feat: Add `whenHas` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 4e151298c86a..e03d286ad1b5 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -269,4 +269,25 @@ public function hasAny($keys) return Arr::hasAny($input, $keys); } + /** + * Apply the callback if the validated inputs contains the given input item key. + * + * @param string $key + * @param callable $callback + * @param callable|null $default + * @return $this|mixed + */ + public function whenHas($key, callable $callback, callable $default = null) + { + if ($this->has($key)) { + return $callback(data_get($this->all(), $key)) ?: $this; + } + + if ($default) { + return $default(); + } + + return $this; + } + } From 996c14be73f2d5d8dbe5333c47f43d30ad4216bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 15:54:47 +0300 Subject: [PATCH 244/325] feat: Add `filled` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index e03d286ad1b5..f83a6030e467 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -290,4 +290,23 @@ public function whenHas($key, callable $callback, callable $default = null) return $this; } + /** + * Determine if the validated inputs contains a non-empty value for an input item. + * + * @param string|array $key + * @return bool + */ + public function filled($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if ($this->isEmptyString($value)) { + return false; + } + } + + return true; + } + } From fd3da928163d832803d041390fd1472375b51fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 15:55:14 +0300 Subject: [PATCH 245/325] feat: Add `isNotFilled` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index f83a6030e467..cfcbb36ac16c 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -309,4 +309,23 @@ public function filled($key) return true; } + /** + * Determine if the validated inputs contains an empty value for an input item. + * + * @param string|array $key + * @return bool + */ + public function isNotFilled($key) + { + $keys = is_array($key) ? $key : func_get_args(); + + foreach ($keys as $value) { + if (! $this->isEmptyString($value)) { + return false; + } + } + + return true; + } + } From b46fc126d79adc4e1f1e244e34bd5631d91e3be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 15:55:39 +0300 Subject: [PATCH 246/325] feat: Add `anyFilled` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index cfcbb36ac16c..a78ea20018d4 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -328,4 +328,23 @@ public function isNotFilled($key) return true; } + /** + * Determine if the validated inputs contains a non-empty value for any of the given inputs. + * + * @param string|array $keys + * @return bool + */ + public function anyFilled($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + foreach ($keys as $key) { + if ($this->filled($key)) { + return true; + } + } + + return false; + } + } From f7a3759407d121a4e05f801141311167038e4329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 15:59:08 +0300 Subject: [PATCH 247/325] feat: Add `whenFilled` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index a78ea20018d4..2af84d4ff53f 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -347,4 +347,25 @@ public function anyFilled($keys) return false; } + /** + * Apply the callback if the validated inputs contains a non-empty value for the given input item key. + * + * @param string $key + * @param callable $callback + * @param callable|null $default + * @return $this|mixed + */ + public function whenFilled($key, callable $callback, callable $default = null) + { + if ($this->filled($key)) { + return $callback(data_get($this->all(), $key)) ?: $this; + } + + if ($default) { + return $default(); + } + + return $this; + } + } From c9166fa62f27d737a15f261725284ae0788c845a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:00:04 +0300 Subject: [PATCH 248/325] feat: Add `whenMissing` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 2af84d4ff53f..e4f97b55ec60 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -368,4 +368,25 @@ public function whenFilled($key, callable $callback, callable $default = null) return $this; } + /** + * Apply the callback if the validated inputs is missing the given input item key. + * + * @param string $key + * @param callable $callback + * @param callable|null $default + * @return $this|mixed + */ + public function whenMissing($key, callable $callback, callable $default = null) + { + if ($this->missing($key)) { + return $callback(data_get($this->all(), $key)) ?: $this; + } + + if ($default) { + return $default(); + } + + return $this; + } + } From 8ef586fbcbd27cc815a754b504e6c6c32713cd24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:00:47 +0300 Subject: [PATCH 249/325] feat: Add `isEmptyString` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index e4f97b55ec60..e67019f44449 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -389,4 +389,17 @@ public function whenMissing($key, callable $callback, callable $default = null) return $this; } + /** + * Determine if the given input key is an empty string for "filled". + * + * @param string $key + * @return bool + */ + protected function isEmptyString($key) + { + $value = $this->input($key); + + return ! is_bool($value) && ! is_array($value) && trim((string) $value) === ''; + } + } From 15f23454bba479d727aca3caeb31b51050873767 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:02:15 +0300 Subject: [PATCH 250/325] feat: Add `keys` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index e67019f44449..d62ff2f3dc43 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -402,4 +402,14 @@ protected function isEmptyString($key) return ! is_bool($value) && ! is_array($value) && trim((string) $value) === ''; } + /** + * Get the keys for all of the input. + * + * @return array + */ + public function keys() + { + return array_keys($this->input()); + } + } From f4174e49ee2af049c5a9f96540db54f1ef2a9132 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:02:41 +0300 Subject: [PATCH 251/325] feat: Add `input` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index d62ff2f3dc43..fa6d0b167b4a 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -412,4 +412,18 @@ public function keys() return array_keys($this->input()); } + /** + * Retrieve an input item from the validated inputs. + * + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function input($key = null, $default = null) + { + return data_get( + $this->all(), $key, $default + ); + } + } From d93d4419e7c08917c2e0919680143e19f12b9648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:03:16 +0300 Subject: [PATCH 252/325] feat: Add `string` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index fa6d0b167b4a..ed7ff41566d7 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -426,4 +426,16 @@ public function input($key = null, $default = null) ); } + /** + * Retrieve input from the validated inputs as a Stringable instance. + * + * @param string $key + * @param mixed $default + * @return \Illuminate\Support\Stringable + */ + public function string($key, $default = null) + { + return str($this->input($key, $default)); + } + } From 19b9d3bf270ea88edb92759a1d0b7e52620b33a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:03:35 +0300 Subject: [PATCH 253/325] feat: Add `str` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index ed7ff41566d7..31febdcf4ebf 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -426,6 +426,18 @@ public function input($key = null, $default = null) ); } + /** + * Retrieve input from the validated inputs as a Stringable instance. + * + * @param string $key + * @param mixed $default + * @return \Illuminate\Support\Stringable + */ + public function str($key, $default = null) + { + return $this->string($key, $default); + } + /** * Retrieve input from the validated inputs as a Stringable instance. * From cbe7fa891b1e095ede77a811c6cb6e5c4340cd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:04:11 +0300 Subject: [PATCH 254/325] feat: Add `boolean` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 31febdcf4ebf..b2567fb9137c 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -450,4 +450,18 @@ public function string($key, $default = null) return str($this->input($key, $default)); } + /** + * Retrieve input as a boolean value. + * + * Returns true when value is "1", "true", "on", and "yes". Otherwise, returns false. + * + * @param string|null $key + * @param bool $default + * @return bool + */ + public function boolean($key = null, $default = false) + { + return filter_var($this->input($key, $default), FILTER_VALIDATE_BOOLEAN); + } + } From 10a9cb4030ef1119e00c0bfe8c1831217ec47c63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:04:32 +0300 Subject: [PATCH 255/325] feat: Add `integer` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index b2567fb9137c..fb4e727931d1 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -464,4 +464,16 @@ public function boolean($key = null, $default = false) return filter_var($this->input($key, $default), FILTER_VALIDATE_BOOLEAN); } + /** + * Retrieve input as an integer value. + * + * @param string $key + * @param int $default + * @return int + */ + public function integer($key, $default = 0) + { + return intval($this->input($key, $default)); + } + } From 17b5db725a720e20080b3ede6ecf8863bcc8728a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:04:52 +0300 Subject: [PATCH 256/325] feat: Add `float` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index fb4e727931d1..e8bd09257ef3 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -476,4 +476,16 @@ public function integer($key, $default = 0) return intval($this->input($key, $default)); } + /** + * Retrieve input as a float value. + * + * @param string $key + * @param float $default + * @return float + */ + public function float($key, $default = 0.0) + { + return floatval($this->input($key, $default)); + } + } From 9ba7d34a8e9c3588242e46468cc171e24ec9edfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:05:35 +0300 Subject: [PATCH 257/325] feat: Add `date` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index e8bd09257ef3..43662f0b240c 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -4,6 +4,7 @@ use ArrayIterator; use Illuminate\Contracts\Support\ValidatedData; +use Illuminate\Support\Facades\Date; use stdClass; use Traversable; @@ -488,4 +489,27 @@ public function float($key, $default = 0.0) return floatval($this->input($key, $default)); } + /** + * Retrieve input from the validated inputs as a Carbon instance. + * + * @param string $key + * @param string|null $format + * @param string|null $tz + * @return \Illuminate\Support\Carbon|null + * + * @throws \Carbon\Exceptions\InvalidFormatException + */ + public function date($key, $format = null, $tz = null) + { + if ($this->isNotFilled($key)) { + return null; + } + + if (is_null($format)) { + return Date::parse($this->input($key), $tz); + } + + return Date::createFromFormat($format, $this->input($key), $tz); + } + } From 6fd3688cd5b9430f3f1e8b129c4819e84c76036a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:06:08 +0300 Subject: [PATCH 258/325] feat: Add `enum` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 43662f0b240c..fc9c0239694e 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -512,4 +512,24 @@ public function date($key, $format = null, $tz = null) return Date::createFromFormat($format, $this->input($key), $tz); } + /** + * Retrieve input from the validated inputs as an enum. + * + * @template TEnum + * + * @param string $key + * @param class-string $enumClass + * @return TEnum|null + */ + public function enum($key, $enumClass) + { + if ($this->isNotFilled($key) || + ! enum_exists($enumClass) || + ! method_exists($enumClass, 'tryFrom')) { + return null; + } + + return $enumClass::tryFrom($this->input($key)); + } + } From 23e99237bfa723824da023c96845589ec85f8e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:06:29 +0300 Subject: [PATCH 259/325] feat: Add `dump` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index fc9c0239694e..2408c592eeec 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -6,6 +6,7 @@ use Illuminate\Contracts\Support\ValidatedData; use Illuminate\Support\Facades\Date; use stdClass; +use Symfony\Component\VarDumper\VarDumper; use Traversable; class ValidatedInput implements ValidatedData @@ -532,4 +533,19 @@ public function enum($key, $enumClass) return $enumClass::tryFrom($this->input($key)); } + /** + * Dump the items. + * + * @param mixed $keys + * @return $this + */ + public function dump($keys = []) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + VarDumper::dump(count($keys) > 0 ? $this->only($keys) : $this->all()); + + return $this; + } + } From 2c3d634d7c575d879ab404717117046ff2d92306 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:07:27 +0300 Subject: [PATCH 260/325] feat: Add `dd` method to `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 2408c592eeec..71830627fce1 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -533,6 +533,19 @@ public function enum($key, $enumClass) return $enumClass::tryFrom($this->input($key)); } + /** + * Dump the validated inputs items and end the script. + * + * @param mixed ...$keys + * @return never + */ + public function dd(...$keys) + { + $this->dump(...$keys); + + exit(1); + } + /** * Dump the items. * From 8ff553ba5b5fd19528a1b8ca23209de4250f926d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:10:57 +0300 Subject: [PATCH 261/325] refactor: Add `$key` prop to `collect` method in `ValidatedInput` --- src/Illuminate/Support/ValidatedInput.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 71830627fce1..cc14ca26af42 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -115,11 +115,12 @@ public function merge(array $items) /** * Get the input as a collection. * + * @param array|string|null $key * @return \Illuminate\Support\Collection */ - public function collect() + public function collect($key = null) { - return new Collection($this->input); + return collect(is_array($key) ? $this->only($key) : $this->input($key)); } /** From 1e9ff59b60c06dc18f1508208ae4d77044b6af87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:12:33 +0300 Subject: [PATCH 262/325] refactor: Change usages `$this->input` to `$this->all()` --- src/Illuminate/Support/ValidatedInput.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index cc14ca26af42..ac144ba343bb 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -40,7 +40,7 @@ public function has($keys) $keys = is_array($keys) ? $keys : func_get_args(); foreach ($keys as $key) { - if (! Arr::has($this->input, $key)) { + if (! Arr::has($this->all(), $key)) { return false; } } @@ -69,7 +69,7 @@ public function only($keys) { $results = []; - $input = $this->input; + $input = $this->all(); $placeholder = new stdClass; @@ -94,7 +94,7 @@ public function except($keys) { $keys = is_array($keys) ? $keys : func_get_args(); - $results = $this->input; + $results = $this->all(); Arr::forget($results, $keys); @@ -109,7 +109,7 @@ public function except($keys) */ public function merge(array $items) { - return new static(array_merge($this->input, $items)); + return new static(array_merge($this->all(), $items)); } /** From 5e97d897a50e4ff0e8b9648ca0f011be52f59285 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:13:50 +0300 Subject: [PATCH 263/325] refactor: Change usages `$this->input[$key]` to `$this->input($key)` --- src/Illuminate/Support/ValidatedInput.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index ac144ba343bb..01cb2aa8be93 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -151,7 +151,7 @@ public function toArray() */ public function __get($name) { - return $this->input[$name]; + return $this->input($name); } /** @@ -206,7 +206,7 @@ public function offsetExists($key): bool */ public function offsetGet($key): mixed { - return $this->input[$key]; + return $this->input($key); } /** From e56333933d253d010488554b75ba8f4cd0b15110 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:14:59 +0300 Subject: [PATCH 264/325] refactor: Change usages `isset($this->input[$name])` to `$this->exists($name)` --- src/Illuminate/Support/ValidatedInput.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 01cb2aa8be93..3cf043037b37 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -173,7 +173,7 @@ public function __set($name, $value) */ public function __isset($name) { - return isset($this->input[$name]); + return $this->exists($name); } /** @@ -195,7 +195,7 @@ public function __unset($name) */ public function offsetExists($key): bool { - return isset($this->input[$key]); + return $this->exists($key); } /** From 72a29827d4213fb168f05b6cb40419fa3eb4f11e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:15:10 +0300 Subject: [PATCH 265/325] test: Added tests --- tests/Support/ValidatedInputTest.php | 491 +++++++++++++++++++++++++++ 1 file changed, 491 insertions(+) diff --git a/tests/Support/ValidatedInputTest.php b/tests/Support/ValidatedInputTest.php index a8bdbe894322..c88117c5899b 100644 --- a/tests/Support/ValidatedInputTest.php +++ b/tests/Support/ValidatedInputTest.php @@ -2,8 +2,12 @@ namespace Illuminate\Tests\Support; +use Illuminate\Support\Collection; +use Illuminate\Support\Stringable; use Illuminate\Support\ValidatedInput; +use Illuminate\Tests\Support\Fixtures\StringBackedEnum; use PHPUnit\Framework\TestCase; +use Illuminate\Support\Carbon; class ValidatedInputTest extends TestCase { @@ -44,4 +48,491 @@ public function test_input_existence() $this->assertEquals(true, $inputB->has(['name', 'votes'])); } + + public function test_exists_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertTrue($input->exists('name')); + $this->assertTrue($input->exists('surname')); + $this->assertTrue($input->exists(['name', 'surname'])); + $this->assertTrue($input->exists('foo.bar')); + $this->assertTrue($input->exists(['name', 'foo.baz'])); + $this->assertTrue($input->exists(['name', 'foo'])); + $this->assertTrue($input->exists('foo')); + + $this->assertFalse($input->exists('votes')); + $this->assertFalse($input->exists(['name', 'votes'])); + $this->assertFalse($input->exists(['votes', 'foo.bar'])); + } + + public function test_has_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertTrue($input->has('name')); + $this->assertTrue($input->has('surname')); + $this->assertTrue($input->has(['name', 'surname'])); + $this->assertTrue($input->has('foo.bar')); + $this->assertTrue($input->has(['name', 'foo.baz'])); + $this->assertTrue($input->has(['name', 'foo'])); + $this->assertTrue($input->has('foo')); + + $this->assertFalse($input->has('votes')); + $this->assertFalse($input->has(['name', 'votes'])); + $this->assertFalse($input->has(['votes', 'foo.bar'])); + } + + public function test_has_any_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertTrue($input->hasAny('name')); + $this->assertTrue($input->hasAny('surname')); + $this->assertTrue($input->hasAny('foo.bar')); + $this->assertTrue($input->hasAny(['name', 'surname'])); + $this->assertTrue($input->hasAny(['name', 'foo.bat'])); + $this->assertTrue($input->hasAny(['votes', 'foo'])); + + $this->assertFalse($input->hasAny('votes')); + $this->assertFalse($input->hasAny(['votes', 'foo.bat'])); + } + + public function test_when_has_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'age' => '', 'foo' => ['bar' => null]]); + + $name = $age = $city = $foo = $bar = $baz = false; + + $input->whenHas('name', function ($value) use (&$name) { + $name = $value; + }); + + $input->whenHas('age', function ($value) use (&$age) { + $age = $value; + }); + + $input->whenHas('city', function ($value) use (&$city) { + $city = $value; + }); + + $input->whenHas('foo', function ($value) use (&$foo) { + $foo = $value; + }); + + + $input->whenHas('foo.bar', function ($value) use (&$bar) { + $bar = $value; + }); + + $input->whenHas('foo.baz', function () use (&$baz) { + $baz = 'test'; + }, function () use (&$baz) { + $baz = true; + }); + + $this->assertSame('Fatih', $name); + $this->assertSame('', $age); + $this->assertFalse($city); + $this->assertEquals(['bar' => null], $foo); + $this->assertTrue($baz); + $this->assertNull($bar); + } + + public function test_filled_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertTrue($input->filled('name')); + $this->assertTrue($input->filled('surname')); + $this->assertTrue($input->filled(['name', 'surname'])); + $this->assertTrue($input->filled(['name', 'foo'])); + $this->assertTrue($input->filled('foo')); + + $this->assertFalse($input->filled('foo.bar')); + $this->assertFalse($input->filled(['name', 'foo.baz'])); + $this->assertFalse($input->filled('votes')); + $this->assertFalse($input->filled(['name', 'votes'])); + $this->assertFalse($input->filled(['votes', 'foo.bar'])); + } + + public function test_is_not_filled_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertFalse($input->isNotFilled('name')); + $this->assertFalse($input->isNotFilled('surname')); + $this->assertFalse($input->isNotFilled(['name', 'surname'])); + $this->assertFalse($input->isNotFilled(['name', 'foo'])); + $this->assertFalse($input->isNotFilled('foo')); + $this->assertFalse($input->isNotFilled(['name', 'foo.baz'])); + $this->assertFalse($input->isNotFilled(['name', 'votes'])); + + $this->assertTrue($input->isNotFilled('foo.bar')); + $this->assertTrue($input->isNotFilled('votes')); + $this->assertTrue($input->isNotFilled(['votes', 'foo.bar'])); + } + + public function test_any_filled_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertTrue($input->anyFilled('name')); + $this->assertTrue($input->anyFilled('surname')); + $this->assertTrue($input->anyFilled(['name', 'surname'])); + $this->assertTrue($input->anyFilled(['name', 'foo'])); + $this->assertTrue($input->anyFilled('foo')); + $this->assertTrue($input->anyFilled(['name', 'foo.baz'])); + $this->assertTrue($input->anyFilled(['name', 'votes'])); + + $this->assertFalse($input->anyFilled('foo.bar')); + $this->assertFalse($input->anyFilled('votes')); + $this->assertFalse($input->anyFilled(['votes', 'foo.bar'])); + } + + public function test_when_filled_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'age' => '', 'foo' => ['bar' => null]]); + + $name = $age = $city = $foo = $bar = $baz = false; + + $input->whenFilled('name', function ($value) use (&$name) { + $name = $value; + }); + + $input->whenFilled('age', function ($value) use (&$age) { + $age = $value; + }); + + $input->whenFilled('city', function ($value) use (&$city) { + $city = $value; + }); + + $input->whenFilled('foo', function ($value) use (&$foo) { + $foo = $value; + }); + + + $input->whenFilled('foo.bar', function ($value) use (&$bar) { + $bar = $value; + }); + + $input->whenFilled('foo.baz', function () use (&$baz) { + $baz = 'test'; + }, function () use (&$baz) { + $baz = true; + }); + + $this->assertSame('Fatih', $name); + $this->assertEquals(['bar' => null], $foo); + $this->assertTrue($baz); + $this->assertFalse($age); + $this->assertFalse($city); + $this->assertFalse($bar); + } + + public function test_missing_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertFalse($input->missing('name')); + $this->assertFalse($input->missing('surname')); + $this->assertFalse($input->missing(['name', 'surname'])); + $this->assertFalse($input->missing('foo.bar')); + $this->assertFalse($input->missing(['name', 'foo.baz'])); + $this->assertFalse($input->missing(['name', 'foo'])); + $this->assertFalse($input->missing('foo')); + + $this->assertTrue($input->missing('votes')); + $this->assertTrue($input->missing(['name', 'votes'])); + $this->assertTrue($input->missing(['votes', 'foo.bar'])); + } + + + public function test_when_missing_method() + { + $input = new ValidatedInput(['foo' => ['bar' => null]]); + + $name = $age = $city = $foo = $bar = $baz = false; + + $input->whenMissing('name', function () use (&$name) { + $name = 'Fatih'; + }); + + $input->whenMissing('age', function () use (&$age) { + $age = ''; + }); + + $input->whenMissing('city', function () use (&$city) { + $city = null; + }); + + $input->whenMissing('foo', function ($value) use (&$foo) { + $foo = $value; + }); + + $input->whenMissing('foo.baz', function () use (&$baz) { + $baz = true; + }); + + $input->whenMissing('foo.bar', function () use (&$bar) { + $bar = 'test'; + }, function () use (&$bar) { + $bar = true; + }); + + $this->assertSame('Fatih', $name); + $this->assertSame('', $age); + $this->assertNull($city); + $this->assertFalse($foo); + $this->assertTrue($baz); + $this->assertTrue($bar); + } + + public function test_keys_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertEquals(['name', 'surname', 'foo'], $input->keys()); + } + + public function test_all_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertEquals(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']], $input->all()); + } + + + public function test_input_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertSame('Fatih', $input->input('name')); + $this->assertSame(null, $input->input('foo.bar')); + $this->assertSame('test', $input->input('foo.bat', 'test')); + } + + public function test_str_method() + { + $input = new ValidatedInput([ + 'int' => 123, + 'int_str' => '456', + 'float' => 123.456, + 'float_str' => '123.456', + 'float_zero' => 0.000, + 'float_str_zero' => '0.000', + 'str' => 'abc', + 'empty_str' => '', + 'null' => null, + ]); + + $this->assertTrue($input->str('int') instanceof Stringable); + $this->assertTrue($input->str('int') instanceof Stringable); + $this->assertTrue($input->str('unknown_key') instanceof Stringable); + $this->assertSame('123', $input->str('int')->value()); + $this->assertSame('456', $input->str('int_str')->value()); + $this->assertSame('123.456', $input->str('float')->value()); + $this->assertSame('123.456', $input->str('float_str')->value()); + $this->assertSame('0', $input->str('float_zero')->value()); + $this->assertSame('0.000', $input->str('float_str_zero')->value()); + $this->assertSame('', $input->str('empty_str')->value()); + $this->assertSame('', $input->str('null')->value()); + $this->assertSame('', $input->str('unknown_key')->value()); + } + + + public function test_string_method() + { + $input = new ValidatedInput([ + 'int' => 123, + 'int_str' => '456', + 'float' => 123.456, + 'float_str' => '123.456', + 'float_zero' => 0.000, + 'float_str_zero' => '0.000', + 'str' => 'abc', + 'empty_str' => '', + 'null' => null, + ]); + + $this->assertTrue($input->string('int') instanceof Stringable); + $this->assertTrue($input->string('int') instanceof Stringable); + $this->assertTrue($input->string('unknown_key') instanceof Stringable); + $this->assertSame('123', $input->string('int')->value()); + $this->assertSame('456', $input->string('int_str')->value()); + $this->assertSame('123.456', $input->string('float')->value()); + $this->assertSame('123.456', $input->string('float_str')->value()); + $this->assertSame('0', $input->string('float_zero')->value()); + $this->assertSame('0.000', $input->string('float_str_zero')->value()); + $this->assertSame('', $input->string('empty_str')->value()); + $this->assertSame('', $input->string('null')->value()); + $this->assertSame('', $input->string('unknown_key')->value()); + } + + + public function test_boolean_method() + { + $input = new ValidatedInput([ + 'with_trashed' => 'false', + 'download' => true, + 'checked' => 1, + 'unchecked' => '0', + 'with_on' => 'on', + 'with_yes' => 'yes' + ]); + + $this->assertTrue($input->boolean('checked')); + $this->assertTrue($input->boolean('download')); + $this->assertFalse($input->boolean('unchecked')); + $this->assertFalse($input->boolean('with_trashed')); + $this->assertFalse($input->boolean('some_undefined_key')); + $this->assertTrue($input->boolean('with_on')); + $this->assertTrue($input->boolean('with_yes')); + } + + + public function test_integer_method() + { + $input = new ValidatedInput([ + 'int' => '123', + 'raw_int' => 456, + 'zero_padded' => '078', + 'space_padded' => ' 901', + 'nan' => 'nan', + 'mixed' => '1ab', + 'underscore_notation' => '2_000', + 'null' => null, + ]); + + $this->assertSame(123, $input->integer('int')); + $this->assertSame(456, $input->integer('raw_int')); + $this->assertSame(78, $input->integer('zero_padded')); + $this->assertSame(901, $input->integer('space_padded')); + $this->assertSame(0, $input->integer('nan')); + $this->assertSame(1, $input->integer('mixed')); + $this->assertSame(2, $input->integer('underscore_notation')); + $this->assertSame(123456, $input->integer('unknown_key', 123456)); + $this->assertSame(0, $input->integer('null')); + $this->assertSame(0, $input->integer('null', 123456)); + } + + + public function test_float_method() + { + $input = new ValidatedInput([ + 'float' => '1.23', + 'raw_float' => 45.6, + 'decimal_only' => '.6', + 'zero_padded' => '0.78', + 'space_padded' => ' 90.1', + 'nan' => 'nan', + 'mixed' => '1.ab', + 'scientific_notation' => '1e3', + 'null' => null, + ]); + + $this->assertSame(1.23, $input->float('float')); + $this->assertSame(45.6, $input->float('raw_float')); + $this->assertSame(.6, $input->float('decimal_only')); + $this->assertSame(0.78, $input->float('zero_padded')); + $this->assertSame(90.1, $input->float('space_padded')); + $this->assertSame(0.0, $input->float('nan')); + $this->assertSame(1.0, $input->float('mixed')); + $this->assertSame(1e3, $input->float('scientific_notation')); + $this->assertSame(123.456, $input->float('unknown_key', 123.456)); + $this->assertSame(0.0, $input->float('null')); + $this->assertSame(0.0, $input->float('null', 123.456)); + } + + + public function test_date_method() + { + $input = new ValidatedInput([ + 'as_null' => null, + 'as_invalid' => 'invalid', + + 'as_datetime' => '24-01-01 16:30:25', + 'as_format' => '1704126625', + 'as_timezone' => '24-01-01 13:30:25', + + 'as_date' => '2024-01-01', + 'as_time' => '16:30:25', + ]); + + $current = Carbon::create(2024, 1, 1, 16, 30, 25); + + $this->assertNull($input->date('as_null')); + $this->assertNull($input->date('doesnt_exists')); + + $this->assertEquals($current, $input->date('as_datetime')); + $this->assertEquals($current->format('Y-m-d H:i:s P'), $input->date('as_format', 'U')->format('Y-m-d H:i:s P')); + $this->assertEquals($current, $input->date('as_timezone', null, 'America/Santiago')); + + $this->assertTrue($input->date('as_date')->isSameDay($current)); + $this->assertTrue($input->date('as_time')->isSameSecond('16:30:25')); + } + + public function test_enum_method() + { + $input = new ValidatedInput([ + 'valid_enum_value' => 'Hello world', + 'invalid_enum_value' => 'invalid', + ]); + + $this->assertNull($input->enum('doesnt_exists', StringBackedEnum::class)); + + $this->assertEquals(StringBackedEnum::HELLO_WORLD, $input->enum('valid_enum_value', StringBackedEnum::class)); + + $this->assertNull($input->enum('invalid_enum_value', StringBackedEnum::class)); + } + + public function test_collect_method() + { + $input = new ValidatedInput(['users' => [1, 2, 3]]); + + $this->assertInstanceOf(Collection::class, $input->collect('users')); + $this->assertTrue($input->collect('developers')->isEmpty()); + $this->assertEquals([1, 2, 3], $input->collect('users')->all()); + $this->assertEquals(['users' => [1, 2, 3]], $input->collect()->all()); + + $input = new ValidatedInput(['text-payload']); + $this->assertEquals(['text-payload'], $input->collect()->all()); + + $input = new ValidatedInput(['email' => 'test@example.com']); + $this->assertEquals(['test@example.com'], $input->collect('email')->all()); + + $input = new ValidatedInput([]); + $this->assertInstanceOf(Collection::class, $input->collect()); + $this->assertTrue($input->collect()->isEmpty()); + + $input = new ValidatedInput(['users' => [1, 2, 3], 'roles' => [4, 5, 6], 'foo' => ['bar', 'baz'], 'email' => 'test@example.com']); + $this->assertInstanceOf(Collection::class, $input->collect(['users'])); + $this->assertTrue($input->collect(['developers'])->isEmpty()); + $this->assertTrue($input->collect(['roles'])->isNotEmpty()); + $this->assertEquals(['roles' => [4, 5, 6]], $input->collect(['roles'])->all()); + $this->assertEquals(['users' => [1, 2, 3], 'email' => 'test@example.com'], $input->collect(['users', 'email'])->all()); + $this->assertEquals(collect(['roles' => [4, 5, 6], 'foo' => ['bar', 'baz']]), $input->collect(['roles', 'foo'])); + $this->assertEquals(['users' => [1, 2, 3], 'roles' => [4, 5, 6], 'foo' => ['bar', 'baz'], 'email' => 'test@example.com'], $input->collect()->all()); + } + + + public function test_only_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertEquals(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null]], $input->only('name', 'surname', 'foo.bar')); + $this->assertEquals(['name' => 'Fatih', 'foo' => ['bar' => null, 'baz' => '']], $input->only('name', 'foo')); + $this->assertEquals(['foo' => ['baz' => '']], $input->only('foo.baz')); + $this->assertEquals(['name' => 'Fatih'], $input->only('name')); + } + + public function test_except_method() + { + $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); + + $this->assertEquals(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null]], $input->except('foo.baz')); + $this->assertEquals(['surname' => 'AYDIN'], $input->except('name', 'foo')); + $this->assertEquals([], $input->except('name', 'surname', 'foo')); + } } From 21502433d14f29442e1f166a32c57997952d5fa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:42:21 +0300 Subject: [PATCH 266/325] style: Fixed styling --- tests/Support/ValidatedInputTest.php | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/tests/Support/ValidatedInputTest.php b/tests/Support/ValidatedInputTest.php index c88117c5899b..6798bd69c914 100644 --- a/tests/Support/ValidatedInputTest.php +++ b/tests/Support/ValidatedInputTest.php @@ -2,12 +2,12 @@ namespace Illuminate\Tests\Support; +use Illuminate\Support\Carbon; use Illuminate\Support\Collection; use Illuminate\Support\Stringable; use Illuminate\Support\ValidatedInput; use Illuminate\Tests\Support\Fixtures\StringBackedEnum; use PHPUnit\Framework\TestCase; -use Illuminate\Support\Carbon; class ValidatedInputTest extends TestCase { @@ -120,7 +120,6 @@ public function test_when_has_method() $foo = $value; }); - $input->whenHas('foo.bar', function ($value) use (&$bar) { $bar = $value; }); @@ -212,7 +211,6 @@ public function test_when_filled_method() $foo = $value; }); - $input->whenFilled('foo.bar', function ($value) use (&$bar) { $bar = $value; }); @@ -248,7 +246,6 @@ public function test_missing_method() $this->assertTrue($input->missing(['votes', 'foo.bar'])); } - public function test_when_missing_method() { $input = new ValidatedInput(['foo' => ['bar' => null]]); @@ -303,7 +300,6 @@ public function test_all_method() $this->assertEquals(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']], $input->all()); } - public function test_input_method() { $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); @@ -341,7 +337,6 @@ public function test_str_method() $this->assertSame('', $input->str('unknown_key')->value()); } - public function test_string_method() { $input = new ValidatedInput([ @@ -370,7 +365,6 @@ public function test_string_method() $this->assertSame('', $input->string('unknown_key')->value()); } - public function test_boolean_method() { $input = new ValidatedInput([ @@ -379,7 +373,7 @@ public function test_boolean_method() 'checked' => 1, 'unchecked' => '0', 'with_on' => 'on', - 'with_yes' => 'yes' + 'with_yes' => 'yes', ]); $this->assertTrue($input->boolean('checked')); @@ -391,7 +385,6 @@ public function test_boolean_method() $this->assertTrue($input->boolean('with_yes')); } - public function test_integer_method() { $input = new ValidatedInput([ @@ -417,7 +410,6 @@ public function test_integer_method() $this->assertSame(0, $input->integer('null', 123456)); } - public function test_float_method() { $input = new ValidatedInput([ @@ -445,7 +437,6 @@ public function test_float_method() $this->assertSame(0.0, $input->float('null', 123.456)); } - public function test_date_method() { $input = new ValidatedInput([ @@ -516,7 +507,6 @@ public function test_collect_method() $this->assertEquals(['users' => [1, 2, 3], 'roles' => [4, 5, 6], 'foo' => ['bar', 'baz'], 'email' => 'test@example.com'], $input->collect()->all()); } - public function test_only_method() { $input = new ValidatedInput(['name' => 'Fatih', 'surname' => 'AYDIN', 'foo' => ['bar' => null, 'baz' => '']]); From 56962e768de0e3c35d265e12800d3ef2e7759b63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fatih=20Ayd=C4=B1n?= Date: Sun, 5 May 2024 16:43:14 +0300 Subject: [PATCH 267/325] style: Fixed styling --- src/Illuminate/Support/ValidatedInput.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Illuminate/Support/ValidatedInput.php b/src/Illuminate/Support/ValidatedInput.php index 3cf043037b37..70bc6c3d2e8c 100644 --- a/src/Illuminate/Support/ValidatedInput.php +++ b/src/Illuminate/Support/ValidatedInput.php @@ -561,5 +561,4 @@ public function dump($keys = []) return $this; } - } From 55b95392812ef97fc94714fb591a670927335f9c Mon Sep 17 00:00:00 2001 From: Tietew Date: Fri, 10 May 2024 02:13:11 +0900 Subject: [PATCH 268/325] fix: incorrect parameter name in SesV2Transport (#51352) --- src/Illuminate/Mail/Transport/SesV2Transport.php | 2 +- tests/Mail/MailSesV2TransportTest.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Mail/Transport/SesV2Transport.php b/src/Illuminate/Mail/Transport/SesV2Transport.php index 876630b9e1be..feb25d61a493 100644 --- a/src/Illuminate/Mail/Transport/SesV2Transport.php +++ b/src/Illuminate/Mail/Transport/SesV2Transport.php @@ -51,7 +51,7 @@ protected function doSend(SentMessage $message): void if ($message->getOriginalMessage() instanceof Message) { foreach ($message->getOriginalMessage()->getHeaders()->all() as $header) { if ($header instanceof MetadataHeader) { - $options['Tags'][] = ['Name' => $header->getKey(), 'Value' => $header->getValue()]; + $options['EmailTags'][] = ['Name' => $header->getKey(), 'Value' => $header->getValue()]; } } } diff --git a/tests/Mail/MailSesV2TransportTest.php b/tests/Mail/MailSesV2TransportTest.php index 7b7821558ac7..a433eae2e88e 100755 --- a/tests/Mail/MailSesV2TransportTest.php +++ b/tests/Mail/MailSesV2TransportTest.php @@ -73,7 +73,7 @@ public function testSend() ->with(m::on(function ($arg) { return $arg['Source'] === 'myself@example.com' && $arg['Destination']['ToAddresses'] === ['me@example.com', 'you@example.com'] && - $arg['Tags'] === [['Name' => 'FooTag', 'Value' => 'TagValue']] && + $arg['EmailTags'] === [['Name' => 'FooTag', 'Value' => 'TagValue']] && strpos($arg['Content']['Raw']['Data'], 'Reply-To: Taylor Otwell ') !== false; })) ->andReturn($sesResult); @@ -111,7 +111,7 @@ public function testSesV2LocalConfiguration() 'region' => 'eu-west-1', 'options' => [ 'ConfigurationSetName' => 'Laravel', - 'Tags' => [ + 'EmailTags' => [ ['Name' => 'Laravel', 'Value' => 'Framework'], ], ], @@ -144,7 +144,7 @@ public function testSesV2LocalConfiguration() $this->assertSame([ 'ConfigurationSetName' => 'Laravel', - 'Tags' => [ + 'EmailTags' => [ ['Name' => 'Laravel', 'Value' => 'Framework'], ], ], $transport->getOptions()); From e4993238d47af5bba05f5095fcefdb960ca865d3 Mon Sep 17 00:00:00 2001 From: Jonas Staudenmeir Date: Mon, 13 May 2024 09:32:15 +0200 Subject: [PATCH 269/325] Fix PHPDoc typo (#51390) --- 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 210d8cbb25a1..b9bacd696082 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -1704,7 +1704,7 @@ public function addNestedWhereQuery($query, $boolean = 'and') * * @param \Illuminate\Contracts\Database\Query\Expression|string $column * @param string $operator - * @param \Closure||\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback + * @param \Closure|\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $callback * @param string $boolean * @return $this */ From 8059bea88f78e431659f6adc982e31e0475d17bc Mon Sep 17 00:00:00 2001 From: Khin Nyunt <33210614+cragonnyunt@users.noreply.github.com> Date: Mon, 13 May 2024 23:37:45 +0630 Subject: [PATCH 270/325] Change ssl: broken pipe to broken pipe (#51388) Without using SSL connection to database does not include string `SSL` in error message --- src/Illuminate/Database/DetectsLostConnections.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index 8cb1187a8bb7..4a0208075e02 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -52,7 +52,7 @@ protected function causedByLostConnection(Throwable $e) 'SSL: Connection timed out', 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.', 'Temporary failure in name resolution', - 'SSL: Broken pipe', + '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 9be528c67fe6f2e15a1c9c2e138319d96d36d1a5 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 13 May 2024 18:20:36 +0100 Subject: [PATCH 271/325] Revert " Change ssl: broken pipe to broken pipe (#51388)" (#51399) This reverts commit 8059bea88f78e431659f6adc982e31e0475d17bc. --- src/Illuminate/Database/DetectsLostConnections.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/DetectsLostConnections.php b/src/Illuminate/Database/DetectsLostConnections.php index 4a0208075e02..8cb1187a8bb7 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -52,7 +52,7 @@ protected function causedByLostConnection(Throwable $e) 'SSL: Connection timed out', 'SQLSTATE[HY000]: General error: 1105 The last transaction was aborted due to Seamless Scaling. Please retry.', 'Temporary failure in name resolution', - 'Broken pipe', + '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 010b5489fc1808b1d4e36c350989deeb80f92407 Mon Sep 17 00:00:00 2001 From: Faissal Wahabali Date: Thu, 16 May 2024 22:33:51 +0100 Subject: [PATCH 272/325] [10.x] Fix `apa` on non ASCII characters (#51428) * fix apa on accented words * fix code style * add more tests to apa with more characters * fix code style * remove empty line --- src/Illuminate/Support/Str.php | 9 +++++---- tests/Support/SupportStrTest.php | 12 ++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index ed1f1b5d6834..6217bb33ef82 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1305,14 +1305,13 @@ public static function apa($value) $minorWords = [ 'and', 'as', 'but', 'for', 'if', 'nor', 'or', 'so', 'yet', 'a', 'an', 'the', 'at', 'by', 'for', 'in', 'of', 'off', 'on', 'per', 'to', 'up', 'via', + 'et', 'ou', 'un', 'une', 'la', 'le', 'les', 'de', 'du', 'des', 'par', 'à', ]; $endPunctuation = ['.', '!', '?', ':', '—', ',']; $words = preg_split('/\s+/', $value, -1, PREG_SPLIT_NO_EMPTY); - $words[0] = ucfirst(mb_strtolower($words[0])); - for ($i = 0; $i < count($words); $i++) { $lowercaseWord = mb_strtolower($words[$i]); @@ -1320,7 +1319,9 @@ public static function apa($value) $hyphenatedWords = explode('-', $lowercaseWord); $hyphenatedWords = array_map(function ($part) use ($minorWords) { - return (in_array($part, $minorWords) && mb_strlen($part) <= 3) ? $part : ucfirst($part); + return (in_array($part, $minorWords) && mb_strlen($part) <= 3) + ? $part + : mb_strtoupper(mb_substr($part, 0, 1)).mb_substr($part, 1); }, $hyphenatedWords); $words[$i] = implode('-', $hyphenatedWords); @@ -1330,7 +1331,7 @@ public static function apa($value) ! ($i === 0 || in_array(mb_substr($words[$i - 1], -1), $endPunctuation))) { $words[$i] = $lowercaseWord; } else { - $words[$i] = ucfirst($lowercaseWord); + $words[$i] = mb_strtoupper(mb_substr($lowercaseWord, 0, 1)).mb_substr($lowercaseWord, 1); } } } diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index e61ae5e4e249..9b996e470544 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -97,6 +97,18 @@ public function testStringApa() $this->assertSame('To Kill a Mockingbird', Str::apa('TO KILL A MOCKINGBIRD')); $this->assertSame('To Kill a Mockingbird', Str::apa('To Kill A Mockingbird')); + $this->assertSame('Être Écrivain Commence par Être un Lecteur.', Str::apa('Être écrivain commence par être un lecteur.')); + $this->assertSame('Être Écrivain Commence par Être un Lecteur.', Str::apa('Être Écrivain Commence par Être un Lecteur.')); + $this->assertSame('Être Écrivain Commence par Être un Lecteur.', Str::apa('ÊTRE ÉCRIVAIN COMMENCE PAR ÊTRE UN LECTEUR.')); + + $this->assertSame("C'est-à-Dire.", Str::apa("c'est-à-dire.")); + $this->assertSame("C'est-à-Dire.", Str::apa("C'est-à-Dire.")); + $this->assertSame("C'est-à-Dire.", Str::apa("C'EsT-À-DIRE.")); + + $this->assertSame('Устное Слово – Не Воробей. Как Только Он Вылетит, Его Не Поймаешь.', Str::apa('устное слово – не воробей. как только он вылетит, его не поймаешь.')); + $this->assertSame('Устное Слово – Не Воробей. Как Только Он Вылетит, Его Не Поймаешь.', Str::apa('Устное Слово – Не Воробей. Как Только Он Вылетит, Его Не Поймаешь.')); + $this->assertSame('Устное Слово – Не Воробей. Как Только Он Вылетит, Его Не Поймаешь.', Str::apa('УСТНОЕ СЛОВО – НЕ ВОРОБЕЙ. КАК ТОЛЬКО ОН ВЫЛЕТИТ, ЕГО НЕ ПОЙМАЕШЬ.')); + $this->assertSame('', Str::apa('')); $this->assertSame(' ', Str::apa(' ')); } From 087bf14d1a4c8900018c0d23d445df09713dcf8c Mon Sep 17 00:00:00 2001 From: Nuno Maduro Date: Fri, 17 May 2024 17:01:54 +0100 Subject: [PATCH 273/325] Fixes engine resolvers leaking memory (#51450) --- src/Illuminate/View/ViewServiceProvider.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/View/ViewServiceProvider.php b/src/Illuminate/View/ViewServiceProvider.php index c40c3b9fc5bb..bfdc27ac7e41 100755 --- a/src/Illuminate/View/ViewServiceProvider.php +++ b/src/Illuminate/View/ViewServiceProvider.php @@ -135,7 +135,7 @@ public function registerEngineResolver() public function registerFileEngine($resolver) { $resolver->register('file', function () { - return new FileEngine($this->app['files']); + return new FileEngine(app()->make('files')); }); } @@ -148,7 +148,7 @@ public function registerFileEngine($resolver) public function registerPhpEngine($resolver) { $resolver->register('php', function () { - return new PhpEngine($this->app['files']); + return new PhpEngine(app()->make('files')); }); } @@ -161,9 +161,14 @@ public function registerPhpEngine($resolver) public function registerBladeEngine($resolver) { $resolver->register('blade', function () { - $compiler = new CompilerEngine($this->app['blade.compiler'], $this->app['files']); + $app = app(); - $this->app->terminating(static function () use ($compiler) { + $compiler = new CompilerEngine( + $app->make('blade.compiler'), + $app->make('files'), + ); + + $app->terminating(static function () use ($compiler) { $compiler->forgetCompiledOrNotExpired(); }); From ac9b6fa27663247e8833a9b939ba842df110566d Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Tue, 21 May 2024 17:23:48 +0200 Subject: [PATCH 274/325] Disable release notes for now --- .github/workflows/releases.yml | 76 +++++++++++++------------- .github/workflows/update-changelog.yml | 9 +++ 2 files changed, 47 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/update-changelog.yml diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 8dcfa62b65c0..0907c89ba782 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -79,41 +79,41 @@ jobs: bash ./bin/release.sh v${{ steps.version.outputs.version }} script_stop: true - - name: Generate release notes - id: generated-notes - uses: RedCrafter07/release-notes-action@main - with: - tag-name: v${{ steps.version.outputs.version }} - token: ${{ secrets.GITHUB_TOKEN }} - branch: ${{ github.ref_name }} - - - name: Cleanup release notes - id: cleaned-notes - run: | - RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## What/d') - RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## New Contributors/,$d') - echo "notes=${RELEASE_NOTES}" >> "$GITHUB_OUTPUT" - env: - RELEASE_NOTES: ${{ steps.generated-notes.outputs.release-notes }} - - - name: Create release - uses: softprops/action-gh-release@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: v${{ steps.version.outputs.version }} - name: v${{ steps.version.outputs.version }} - body: ${{ steps.cleaned-notes.outputs.notes }} - target_commitish: ${{ github.ref_name }} - make_latest: 'legacy' - - update-changelog: - needs: release - - name: Update changelog - - uses: laravel/.github/.github/workflows/update-changelog.yml@main - with: - branch: ${{ github.ref_name }} - version: "v${{ needs.release.outputs.version }}" - notes: ${{ needs.release.outputs.notes }} + # - name: Generate release notes + # id: generated-notes + # uses: RedCrafter07/release-notes-action@main + # with: + # tag-name: v${{ steps.version.outputs.version }} + # token: ${{ secrets.GITHUB_TOKEN }} + # branch: ${{ github.ref_name }} + + # - name: Cleanup release notes + # id: cleaned-notes + # run: | + # RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## What/d') + # RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## New Contributors/,$d') + # echo "notes=${RELEASE_NOTES}" >> "$GITHUB_OUTPUT" + # env: + # RELEASE_NOTES: ${{ steps.generated-notes.outputs.release-notes }} + + # - name: Create release + # uses: softprops/action-gh-release@v2 + # env: + # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # with: + # tag_name: v${{ steps.version.outputs.version }} + # name: v${{ steps.version.outputs.version }} + # body: ${{ steps.cleaned-notes.outputs.notes }} + # target_commitish: ${{ github.ref_name }} + # make_latest: 'legacy' + + # update-changelog: + # needs: release + + # name: Update changelog + + # uses: laravel/.github/.github/workflows/update-changelog.yml@main + # with: + # branch: ${{ github.ref_name }} + # version: "v${{ needs.release.outputs.version }}" + # notes: ${{ needs.release.outputs.notes }} diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml new file mode 100644 index 000000000000..1625bda1002c --- /dev/null +++ b/.github/workflows/update-changelog.yml @@ -0,0 +1,9 @@ +name: update changelog + +on: + release: + types: [released] + +jobs: + update: + uses: laravel/.github/.github/workflows/update-changelog.yml@main From 4b60ec334203be3f082dd6213ba099360234a386 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Tue, 21 May 2024 14:37:01 -0300 Subject: [PATCH 275/325] do not use app() foundation helper (#51522) --- src/Illuminate/View/ViewServiceProvider.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/View/ViewServiceProvider.php b/src/Illuminate/View/ViewServiceProvider.php index bfdc27ac7e41..41cd8b93c9aa 100755 --- a/src/Illuminate/View/ViewServiceProvider.php +++ b/src/Illuminate/View/ViewServiceProvider.php @@ -2,6 +2,7 @@ namespace Illuminate\View; +use Illuminate\Container\Container; use Illuminate\Support\ServiceProvider; use Illuminate\View\Compilers\BladeCompiler; use Illuminate\View\Engines\CompilerEngine; @@ -135,7 +136,7 @@ public function registerEngineResolver() public function registerFileEngine($resolver) { $resolver->register('file', function () { - return new FileEngine(app()->make('files')); + return new FileEngine(Container::getInstance()->make('files')); }); } @@ -148,7 +149,7 @@ public function registerFileEngine($resolver) public function registerPhpEngine($resolver) { $resolver->register('php', function () { - return new PhpEngine(app()->make('files')); + return new PhpEngine(Container::getInstance()->make('files')); }); } @@ -161,7 +162,7 @@ public function registerPhpEngine($resolver) public function registerBladeEngine($resolver) { $resolver->register('blade', function () { - $app = app(); + $app = Container::getInstance(); $compiler = new CompilerEngine( $app->make('blade.compiler'), From afdc2b03bd8d126446583da5414d659491aa4f54 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 21 May 2024 17:53:51 +0000 Subject: [PATCH 276/325] Update version to v10.48.11 --- 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 f419828c6430..a3d99698b1cc 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.10'; + const VERSION = '10.48.11'; /** * The base path for the Laravel installation. From d9ecc226d57e2bf5fd219e74d2de9450acb2ed94 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 21 May 2024 17:55:20 +0000 Subject: [PATCH 277/325] Update CHANGELOG --- CHANGELOG.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5d03da62763..07d6bc51c199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.10...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.11...10.x) + +## [v10.48.11](https://github.com/laravel/framework/compare/v10.48.10...v10.48.11) - 2024-05-21 + +* [10.x] Backport: Fix SesV2Transport to use correct `EmailTags` argument by [@Tietew](https://github.com/Tietew) in https://github.com/laravel/framework/pull/51352 +* [10.x] Fix PHPDoc typo by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/51390 +* [10.x] Fix `apa` on non ASCII characters by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/51428 +* [10.x] Fixes view engine resolvers leaking memory by [@nunomaduro](https://github.com/nunomaduro) in https://github.com/laravel/framework/pull/51450 +* [10.x] Do not use `app()` Foundation helper on `ViewServiceProvider` by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/51522 ## [v10.48.10](https://github.com/laravel/framework/compare/v10.48.9...v10.48.10) - 2024-04-30 From 240c8a09123997a7a61a72a7a035eae9d2e02d6d Mon Sep 17 00:00:00 2001 From: Issei <52270000+Issei0804-ie@users.noreply.github.com> Date: Wed, 22 May 2024 23:58:29 +0900 Subject: [PATCH 278/325] fix typo (#51535) --- tests/Validation/ValidationRequiredIfTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Validation/ValidationRequiredIfTest.php b/tests/Validation/ValidationRequiredIfTest.php index 334ba2ea03ce..f0122fb3a02d 100644 --- a/tests/Validation/ValidationRequiredIfTest.php +++ b/tests/Validation/ValidationRequiredIfTest.php @@ -9,7 +9,7 @@ class ValidationRequiredIfTest extends TestCase { - public function testItClousureReturnsFormatsAStringVersionOfTheRule() + public function testItClosureReturnsFormatsAStringVersionOfTheRule() { $rule = new RequiredIf(function () { return true; From 80338e1260dec2823155b9682924ef226dc0fd0b Mon Sep 17 00:00:00 2001 From: Jonas Staudenmeir Date: Thu, 23 May 2024 20:38:25 +0200 Subject: [PATCH 279/325] Fix SQL Server detection in database store (#51547) --- src/Illuminate/Cache/DatabaseStore.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Illuminate/Cache/DatabaseStore.php b/src/Illuminate/Cache/DatabaseStore.php index e5b72107ce96..b8026e3e0de9 100755 --- a/src/Illuminate/Cache/DatabaseStore.php +++ b/src/Illuminate/Cache/DatabaseStore.php @@ -158,9 +158,7 @@ public function add($key, $value, $seconds) $value = $this->serialize($value); $expiration = $this->getTime() + $seconds; - $doesntSupportInsertOrIgnore = [SqlServerConnection::class]; - - if (! in_array(get_class($this->getConnection()), $doesntSupportInsertOrIgnore)) { + if (! $this->getConnection() instanceof SqlServerConnection) { return $this->table()->insertOrIgnore(compact('key', 'value', 'expiration')) > 0; } From 7d7ab3b024544288701ff322654da7aefaea5b79 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 24 May 2024 10:02:04 +0200 Subject: [PATCH 280/325] Add RELEASE.md --- RELEASE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000000..953dc1778d31 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,5 @@ +# Release Instructions + +Go to the ["manual release" GitHub Action](https://github.com/laravel/framework/actions/workflows/releases.yml). Then, choose "Run workflow", select the correct branch, and enter the version you wish to release. Next, press "Run workflow" to execute the action. The workflow will automatically update the version in `Application.php`, tag a new release, run the splitter script for the Illuminate components, generate release notes, create a GitHub Release, and update the `CHANGELOG.md` file. + +Screenshot 2024-05-06 at 10 46 04 From 69b244063f2c57763bd13be50a8e9d4125720609 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 24 May 2024 12:14:54 +0200 Subject: [PATCH 281/325] Fix generating release notes --- .github/workflows/releases.yml | 127 +++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 54 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 0907c89ba782..6045541d3aaa 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -27,8 +27,9 @@ jobs: - name: Remove optional "v" prefix id: version run: | - VERSION=${{ inputs.version }} echo "version=${VERSION#v}" >> "$GITHUB_OUTPUT" + env: + VERSION: ${{ inputs.version }} - name: Check if branch and version match id: guard @@ -36,9 +37,6 @@ jobs: MAJOR_VERSION="${NUMERIC_VERSION%%.*}" BRANCH_MAJOR_VERSION="${BRANCH%%.*}" - echo "MAJOR_VERSION=$(echo $MAJOR_VERSION)" >> $GITHUB_OUTPUT; - echo "BRANCH_MAJOR_VERSION=$(echo $BRANCH_MAJOR_VERSION)" >> $GITHUB_OUTPUT; - if [ "$MAJOR_VERSION" != "$BRANCH_MAJOR_VERSION" ]; then echo "Mismatched versions! Aborting." VERSION_MISMATCH='true'; @@ -47,7 +45,7 @@ jobs: VERSION_MISMATCH='false'; fi - echo "VERSION_MISMATCH=$(echo $VERSION_MISMATCH)" >> $GITHUB_OUTPUT; + echo "VERSION_MISMATCH=$(echo $VERSION_MISMATCH)" >> "$GITHUB_OUTPUT"; env: BRANCH: ${{ github.ref_name }} NUMERIC_VERSION: ${{ steps.version.outputs.version }} @@ -67,53 +65,74 @@ jobs: with: commit_message: "Update version to v${{ steps.version.outputs.version }}" - - name: SSH into splitter server - uses: appleboy/ssh-action@master + # - name: SSH into splitter server + # uses: appleboy/ssh-action@master + # with: + # host: 104.248.56.26 + # username: forge + # key: ${{ secrets.SSH_PRIVATE_KEY_SPLITTER }} + # script: | + # cd laravel-${{ github.ref_name }} + # git pull origin ${{ github.ref_name }} + # bash ./bin/release.sh v${{ steps.version.outputs.version }} + # script_stop: true + + - name: Generate release notes + id: generated-notes + uses: RedCrafter07/release-notes-action@main with: - host: 104.248.56.26 - username: forge - key: ${{ secrets.SSH_PRIVATE_KEY_SPLITTER }} - script: | - cd laravel-${{ github.ref_name }} - git pull origin ${{ github.ref_name }} - bash ./bin/release.sh v${{ steps.version.outputs.version }} - script_stop: true - - # - name: Generate release notes - # id: generated-notes - # uses: RedCrafter07/release-notes-action@main - # with: - # tag-name: v${{ steps.version.outputs.version }} - # token: ${{ secrets.GITHUB_TOKEN }} - # branch: ${{ github.ref_name }} - - # - name: Cleanup release notes - # id: cleaned-notes - # run: | - # RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## What/d') - # RELEASE_NOTES=$(echo $RELEASE_NOTES | sed '/## New Contributors/,$d') - # echo "notes=${RELEASE_NOTES}" >> "$GITHUB_OUTPUT" - # env: - # RELEASE_NOTES: ${{ steps.generated-notes.outputs.release-notes }} - - # - name: Create release - # uses: softprops/action-gh-release@v2 - # env: - # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # with: - # tag_name: v${{ steps.version.outputs.version }} - # name: v${{ steps.version.outputs.version }} - # body: ${{ steps.cleaned-notes.outputs.notes }} - # target_commitish: ${{ github.ref_name }} - # make_latest: 'legacy' - - # update-changelog: - # needs: release - - # name: Update changelog - - # uses: laravel/.github/.github/workflows/update-changelog.yml@main - # with: - # branch: ${{ github.ref_name }} - # version: "v${{ needs.release.outputs.version }}" - # notes: ${{ needs.release.outputs.notes }} + tag-name: v${{ steps.version.outputs.version }} + token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.ref_name }} + + - name: Cleanup release notes + id: cleaned-notes + run: | + START_FROM=$(echo -n "$RELEASE_NOTES" | awk "/What's Changed/{ print NR; exit }" -) + DROP_FROM_CONTRIBUTORS=$(echo -n "$RELEASE_NOTES" | awk "/New Contributors/{ print NR; exit }" -) + DROP_FROM_FULL_CHANGELOG=$(echo -n "$RELEASE_NOTES" | awk "/Full Changelog/{ print NR; exit }" -) + + # Drop everything starting from "Full Changelog" + if [ ! -z "$DROP_FROM_FULL_CHANGELOG" ]; then + RELEASE_NOTES=$(echo -n "$RELEASE_NOTES" | sed "${DROP_FROM_FULL_CHANGELOG},$ d") + fi + + # Drop everything starting from "New Contributors" + if [ ! -z "$DROP_FROM_CONTRIBUTORS" ]; then + RELEASE_NOTES=$(echo -n "$RELEASE_NOTES" | sed "${DROP_FROM_CONTRIBUTORS},$ d") + fi + + # Drop the line "What's Changed" + if [ ! -z "$START_FROM" ]; then + RELEASE_NOTES=$(echo -n "$RELEASE_NOTES" | sed "${START_FROM}d") + fi + + { + echo 'notes<> "$GITHUB_OUTPUT"; + env: + RELEASE_NOTES: ${{ steps.generated-notes.outputs.release-notes }} + + - name: Create release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.version.outputs.version }} + name: v${{ steps.version.outputs.version }} + body: ${{ steps.cleaned-notes.outputs.notes }} + target_commitish: ${{ github.ref_name }} + make_latest: "${{ github.ref_name == github.event.repository.default_branch }}" + + update-changelog: + needs: release + + name: Update changelog + + uses: laravel/.github/.github/workflows/update-changelog.yml@main + with: + branch: ${{ github.ref_name }} + version: "v${{ needs.release.outputs.version }}" + notes: ${{ needs.release.outputs.notes }} From 644e5688c8ec2b4e8769230ac77420680e869d03 Mon Sep 17 00:00:00 2001 From: Jeferson Ortega <70250632+jeffortegad@users.noreply.github.com> Date: Fri, 24 May 2024 14:00:27 -0300 Subject: [PATCH 282/325] [10.x] - Fix batch list loading in Horizon when serialization error occurred (#51551) Co-authored-by: Jeferson Ortega --- src/Illuminate/Bus/DatabaseBatchRepository.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Bus/DatabaseBatchRepository.php b/src/Illuminate/Bus/DatabaseBatchRepository.php index 4333c515ac79..5a7aadde66bd 100644 --- a/src/Illuminate/Bus/DatabaseBatchRepository.php +++ b/src/Illuminate/Bus/DatabaseBatchRepository.php @@ -6,10 +6,10 @@ use Closure; use DateTimeInterface; use Illuminate\Database\Connection; -use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\PostgresConnection; use Illuminate\Database\Query\Expression; use Illuminate\Support\Str; +use Throwable; class DatabaseBatchRepository implements PrunableBatchRepository { @@ -352,7 +352,7 @@ protected function unserialize($serialized) try { return unserialize($serialized); - } catch (ModelNotFoundException) { + } catch (Throwable) { return []; } } From 1d902e968a27e0d00ea48dba0b5011548d455874 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Mon, 27 May 2024 13:26:23 +0200 Subject: [PATCH 283/325] wip --- types/Support/Helpers.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/Support/Helpers.php b/types/Support/Helpers.php index 92257485d96c..b91b648e3f3d 100644 --- a/types/Support/Helpers.php +++ b/types/Support/Helpers.php @@ -32,7 +32,7 @@ return 10; })); -assertType('mixed', with(new User(), function ($user) { +assertType('User', with(new User(), function ($user) { return $user; })); assertType('User', with(new User(), function ($user): User { From 259c5f6d8dcaf854e5f416e728153d0371be41a0 Mon Sep 17 00:00:00 2001 From: CAAHS <154250652+CAAHS@users.noreply.github.com> Date: Mon, 27 May 2024 15:41:52 +0200 Subject: [PATCH 284/325] Fixes explicit route binding with `BackedEnum` (#51586) Port a078b1a97e (Fixes explicit route binding with `BackedEnum` (#51525), 2024-05-21 crynobone) to 10.x (from 11.x). fixes #51583 refs #51514 Signed-off-by: CAAHS Co-authored-by: CAAHS --- .../Routing/ImplicitRouteBinding.php | 4 +++- .../Routing/CategoryBackedEnum.php | 18 ++++++++++++++++++ tests/Integration/Routing/Enums.php | 9 --------- .../ImplicitBackedEnumRouteBindingTest.php | 19 ++++++++++++++++--- 4 files changed, 37 insertions(+), 13 deletions(-) create mode 100644 tests/Integration/Routing/CategoryBackedEnum.php delete mode 100644 tests/Integration/Routing/Enums.php diff --git a/src/Illuminate/Routing/ImplicitRouteBinding.php b/src/Illuminate/Routing/ImplicitRouteBinding.php index d3590de1d707..f8352e3d57e1 100644 --- a/src/Illuminate/Routing/ImplicitRouteBinding.php +++ b/src/Illuminate/Routing/ImplicitRouteBinding.php @@ -86,7 +86,9 @@ protected static function resolveBackedEnumsForRoute($route, $parameters) $backedEnumClass = $parameter->getType()?->getName(); - $backedEnum = $backedEnumClass::tryFrom((string) $parameterValue); + $backedEnum = $parameterValue instanceof $backedEnumClass + ? $parameterValue + : $backedEnumClass::tryFrom((string) $parameterValue); if (is_null($backedEnum)) { throw new BackedEnumCaseNotFoundException($backedEnumClass, $parameterValue); diff --git a/tests/Integration/Routing/CategoryBackedEnum.php b/tests/Integration/Routing/CategoryBackedEnum.php new file mode 100644 index 000000000000..2ff3cf6e7f80 --- /dev/null +++ b/tests/Integration/Routing/CategoryBackedEnum.php @@ -0,0 +1,18 @@ + self::People, + 'c02' => self::Fruits, + default => null, + }; + } +} diff --git a/tests/Integration/Routing/Enums.php b/tests/Integration/Routing/Enums.php deleted file mode 100644 index 5a2ba0f5a8b1..000000000000 --- a/tests/Integration/Routing/Enums.php +++ /dev/null @@ -1,9 +0,0 @@ -value; })->middleware('web'); + Route::bind('categoryCode', fn (string $categoryCode) => CategoryBackedEnum::fromCode($categoryCode) ?? abort(404)); + + Route::post('/categories-code/{categoryCode}', function (CategoryBackedEnum $categoryCode) { + return $categoryCode->value; + })->middleware(['web']); + $response = $this->post('/categories/fruits'); $response->assertSee('fruits'); @@ -68,7 +72,7 @@ public function testWithoutRouteCachingEnabled() $response->assertSee('people'); $response = $this->post('/categories/cars'); - $response->assertNotFound(404); + $response->assertNotFound(); $response = $this->post('/categories-default/'); $response->assertSee('fruits'); @@ -78,5 +82,14 @@ public function testWithoutRouteCachingEnabled() $response = $this->post('/categories-default/fruits'); $response->assertSee('fruits'); + + $response = $this->post('/categories-code/c01'); + $response->assertSee('people'); + + $response = $this->post('/categories-code/c02'); + $response->assertSee('fruits'); + + $response = $this->post('/categories-code/00'); + $response->assertNotFound(); } } From df9b9a5f55e0e388e178c47ef3ae4351f0b88145 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Mon, 27 May 2024 17:45:17 +0200 Subject: [PATCH 285/325] wip --- .github/workflows/update-changelog.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 .github/workflows/update-changelog.yml diff --git a/.github/workflows/update-changelog.yml b/.github/workflows/update-changelog.yml deleted file mode 100644 index 1625bda1002c..000000000000 --- a/.github/workflows/update-changelog.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: update changelog - -on: - release: - types: [released] - -jobs: - update: - uses: laravel/.github/.github/workflows/update-changelog.yml@main From ccf500c4315a19a9fbd3b637fd66aa6c710b0804 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Mon, 27 May 2024 17:50:40 +0200 Subject: [PATCH 286/325] wip --- .github/workflows/releases.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/releases.yml b/.github/workflows/releases.yml index 6045541d3aaa..c4b0cf5490ee 100644 --- a/.github/workflows/releases.yml +++ b/.github/workflows/releases.yml @@ -65,17 +65,17 @@ jobs: with: commit_message: "Update version to v${{ steps.version.outputs.version }}" - # - name: SSH into splitter server - # uses: appleboy/ssh-action@master - # with: - # host: 104.248.56.26 - # username: forge - # key: ${{ secrets.SSH_PRIVATE_KEY_SPLITTER }} - # script: | - # cd laravel-${{ github.ref_name }} - # git pull origin ${{ github.ref_name }} - # bash ./bin/release.sh v${{ steps.version.outputs.version }} - # script_stop: true + - name: SSH into splitter server + uses: appleboy/ssh-action@master + with: + host: 104.248.56.26 + username: forge + key: ${{ secrets.SSH_PRIVATE_KEY_SPLITTER }} + script: | + cd laravel-${{ github.ref_name }} + git pull origin ${{ github.ref_name }} + bash ./bin/release.sh v${{ steps.version.outputs.version }} + script_stop: true - name: Generate release notes id: generated-notes From 590afea38e708022662629fbf5184351fa82cf08 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 28 May 2024 15:46:19 +0000 Subject: [PATCH 287/325] Update version to v10.48.12 --- 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 a3d99698b1cc..c2c651221107 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.11'; + const VERSION = '10.48.12'; /** * The base path for the Laravel installation. From 8a73407cbfb4ae82fa65366077a9e6150870c047 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 28 May 2024 15:47:36 +0000 Subject: [PATCH 288/325] Update CHANGELOG --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07d6bc51c199..9eb661fe6817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.11...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.12...10.x) + +## [v10.48.12](https://github.com/laravel/framework/compare/v10.48.11...v10.48.12) - 2024-05-28 + +* [10.x] Fix typo by [@Issei0804-ie](https://github.com/Issei0804-ie) in https://github.com/laravel/framework/pull/51535 +* [10.x] Fix SQL Server detection in database store by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/51547 +* [10.x] - Fix batch list loading in Horizon when serialization error by [@jeffortegad](https://github.com/jeffortegad) in https://github.com/laravel/framework/pull/51551 +* [10.x] Fixes explicit route binding with `BackedEnum` by [@CAAHS](https://github.com/CAAHS) in https://github.com/laravel/framework/pull/51586 ## [v10.48.11](https://github.com/laravel/framework/compare/v10.48.10...v10.48.11) - 2024-05-21 From 0bf2a4c3558d10520f89ff92771e64f5167a266b Mon Sep 17 00:00:00 2001 From: zds <49744633+zds-s@users.noreply.github.com> Date: Mon, 3 Jun 2024 15:26:07 +0800 Subject: [PATCH 289/325] fix MailManager::createSesV2Transport typo (#51688) --- src/Illuminate/Mail/MailManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Mail/MailManager.php b/src/Illuminate/Mail/MailManager.php index f821aa933e40..0c54fa23b316 100644 --- a/src/Illuminate/Mail/MailManager.php +++ b/src/Illuminate/Mail/MailManager.php @@ -259,7 +259,7 @@ protected function createSesTransport(array $config) * Create an instance of the Symfony Amazon SES V2 Transport driver. * * @param array $config - * @return \Illuminate\Mail\Transport\Se2VwTransport + * @return \Illuminate\Mail\Transport\SesV2Transport */ protected function createSesV2Transport(array $config) { From 8154eb6e4b9673f332e2c26daf7730e409d443cc Mon Sep 17 00:00:00 2001 From: Faissal Wahabali Date: Mon, 3 Jun 2024 14:02:27 +0100 Subject: [PATCH 290/325] [10.x] Fix collection shift less than one item (#51686) * fix collection shift less than 1 * throw exception earlier * Update Collection.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/Collection.php | 13 ++++++++++--- tests/Support/SupportCollectionTest.php | 11 +++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index c46fc052f972..6a225dce5c25 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\Support\CanBeEscapedWhenCastToString; use Illuminate\Support\Traits\EnumeratesValues; use Illuminate\Support\Traits\Macroable; +use InvalidArgumentException; use stdClass; use Traversable; @@ -1124,17 +1125,23 @@ public function search($value, $strict = false) * * @param int $count * @return static|TValue|null + * + * @throws \InvalidArgumentException */ public function shift($count = 1) { - if ($count === 1) { - return array_shift($this->items); + if ($count < 0) { + throw new InvalidArgumentException('Number of shifted items may not be less than zero.'); } - if ($this->isEmpty()) { + if ($count === 0 || $this->isEmpty()) { return new static; } + if ($count === 1) { + return array_shift($this->items); + } + $results = []; $collectionCount = $this->count(); diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 6eb4f372bf44..8f72a9ca1e5e 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -391,6 +391,17 @@ public function testShiftReturnsAndRemovesFirstXItemsInCollection() $this->assertSame('baz', $data->first()); $this->assertEquals(new Collection(['foo', 'bar', 'baz']), (new Collection(['foo', 'bar', 'baz']))->shift(6)); + + $data = new Collection(['foo', 'bar', 'baz']); + + $this->assertEquals(new Collection([]), $data->shift(0)); + $this->assertEquals(collect(['foo', 'bar', 'baz']), $data); + + $this->expectException('InvalidArgumentException'); + (new Collection(['foo', 'bar', 'baz']))->shift(-1); + + $this->expectException('InvalidArgumentException'); + (new Collection(['foo', 'bar', 'baz']))->shift(-2); } /** From 6de257e066f7193b077564a56e3eb9f57faf505b Mon Sep 17 00:00:00 2001 From: Faissal Wahabali Date: Tue, 4 Jun 2024 14:31:20 +0100 Subject: [PATCH 291/325] [10.x] Turn `Enumerable unless()` $callback parameter optional (#51701) * turn Enumerable unless() parameter optional * nullable parameters declaration --- src/Illuminate/Collections/Enumerable.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 918f64758e66..c9fb24fa1fac 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -338,11 +338,11 @@ public function whenNotEmpty(callable $callback, callable $default = null); * @template TUnlessReturnType * * @param bool $value - * @param (callable($this): TUnlessReturnType) $callback + * @param (callable($this): TUnlessReturnType)|null $callback * @param (callable($this): TUnlessReturnType)|null $default * @return $this|TUnlessReturnType */ - public function unless($value, callable $callback, callable $default = null); + public function unless($value, ?callable $callback = null, ?callable $default = null); /** * Apply the callback unless the collection is empty. From c66290f9656cfca2bea9e7646605fc8e3924b988 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 4 Jun 2024 08:31:47 -0500 Subject: [PATCH 292/325] Revert "[10.x] Turn `Enumerable unless()` $callback parameter optional (#51701)" (#51707) This reverts commit 6de257e066f7193b077564a56e3eb9f57faf505b. --- src/Illuminate/Collections/Enumerable.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index c9fb24fa1fac..918f64758e66 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -338,11 +338,11 @@ public function whenNotEmpty(callable $callback, callable $default = null); * @template TUnlessReturnType * * @param bool $value - * @param (callable($this): TUnlessReturnType)|null $callback + * @param (callable($this): TUnlessReturnType) $callback * @param (callable($this): TUnlessReturnType)|null $default * @return $this|TUnlessReturnType */ - public function unless($value, ?callable $callback = null, ?callable $default = null); + public function unless($value, callable $callback, callable $default = null); /** * Apply the callback unless the collection is empty. From 2c6816d697a4362c09c066118addda251b70b98a Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 18 Jun 2024 16:46:35 +0000 Subject: [PATCH 293/325] Update version to v10.48.13 --- 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 c2c651221107..dfb2f3cd12ae 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.12'; + const VERSION = '10.48.13'; /** * The base path for the Laravel installation. From a7538760e2dfb744e1e6d26bae16d75749f343f2 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 18 Jun 2024 16:47:59 +0000 Subject: [PATCH 294/325] Update CHANGELOG --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb661fe6817..8569c1b840ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.12...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.13...10.x) + +## [v10.48.13](https://github.com/laravel/framework/compare/v10.48.12...v10.48.13) - 2024-06-18 + +* [10.x] Fix typo in return comment of createSesTransport method by [@zds-s](https://github.com/zds-s) in https://github.com/laravel/framework/pull/51688 +* [10.x] Fix collection shift less than one item by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/51686 +* [10.x] Turn `Enumerable unless()` $callback parameter optional by [@faissaloux](https://github.com/faissaloux) in https://github.com/laravel/framework/pull/51701 +* Revert "[10.x] Turn `Enumerable unless()` $callback parameter optional" by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/51707 ## [v10.48.12](https://github.com/laravel/framework/compare/v10.48.11...v10.48.12) - 2024-05-28 From d3f16e857ecf542a82cf18cebb0b2c1e56dfccc8 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 19 Jun 2024 01:24:16 +0800 Subject: [PATCH 295/325] [10.x] Fixes unable to call another command as a initialized instance of `Command` class. (#51824) fixes #51822 Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Console/Command.php | 10 +++-- .../Integration/Console/CallCommandsTest.php | 38 +++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 tests/Integration/Console/CallCommandsTest.php diff --git a/src/Illuminate/Console/Command.php b/src/Illuminate/Console/Command.php index 7e1b3a1ff6ed..1c6d949fd12f 100755 --- a/src/Illuminate/Console/Command.php +++ b/src/Illuminate/Console/Command.php @@ -236,11 +236,13 @@ protected function commandIsolationMutex() */ protected function resolveCommand($command) { - if (! class_exists($command)) { - return $this->getApplication()->find($command); - } + if (is_string($command)) { + if (! class_exists($command)) { + return $this->getApplication()->find($command); + } - $command = $this->laravel->make($command); + $command = $this->laravel->make($command); + } if ($command instanceof SymfonyCommand) { $command->setApplication($this->getApplication()); diff --git a/tests/Integration/Console/CallCommandsTest.php b/tests/Integration/Console/CallCommandsTest.php new file mode 100644 index 000000000000..4724b538aec1 --- /dev/null +++ b/tests/Integration/Console/CallCommandsTest.php @@ -0,0 +1,38 @@ +afterApplicationCreated(function () { + Artisan::command('test:a', function () { + $this->call('view:clear'); + }); + + Artisan::command('test:b', function () { + $this->call(ViewClearCommand::class); + }); + + Artisan::command('test:c', function () { + $this->call($this->laravel->make(ViewClearCommand::class)); + }); + }); + + parent::setUp(); + } + + #[TestWith(['test:a'])] + #[TestWith(['test:b'])] + #[TestWith(['test:c'])] + public function testItCanCallCommands(string $command) + { + $this->artisan($command)->assertSuccessful(); + } +} From 16472d1bce7b7619b3dceadbc2349053855397e6 Mon Sep 17 00:00:00 2001 From: Tonko Mulder Date: Wed, 19 Jun 2024 16:25:05 +0200 Subject: [PATCH 296/325] [10.x] fix handle `shift()` on an empty collection (#51841) * add test for collection shift on a empty collection * fix collection shift when dealing with an empty collection * place the `isEmpty()` check before the count check * update naming and assert the actual values --- src/Illuminate/Collections/Collection.php | 6 +++++- tests/Support/SupportCollectionTest.php | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index 6a225dce5c25..d7369d686591 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -1134,7 +1134,11 @@ public function shift($count = 1) throw new InvalidArgumentException('Number of shifted items may not be less than zero.'); } - if ($count === 0 || $this->isEmpty()) { + if ($this->isEmpty()) { + return null; + } + + if ($count === 0) { return new static; } diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index 8f72a9ca1e5e..e1e0a2d89cdb 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -404,6 +404,23 @@ public function testShiftReturnsAndRemovesFirstXItemsInCollection() (new Collection(['foo', 'bar', 'baz']))->shift(-2); } + public function testShiftReturnsNullOnEmptyCollection() + { + $itemFoo = new \stdClass(); + $itemFoo->text = 'f'; + $itemBar = new \stdClass(); + $itemBar->text = 'x'; + + $items = collect([$itemFoo, $itemBar]); + + $foo = $items->shift(); + $bar = $items->shift(); + + $this->assertSame('f', $foo?->text); + $this->assertSame('x', $bar?->text); + $this->assertNull($items->shift()); + } + /** * @dataProvider collectionClassProvider */ From f7ea4e912ce9bacfdb327de4648d07b4c7ee3cb7 Mon Sep 17 00:00:00 2001 From: NickSdot <32384907+NickSdot@users.noreply.github.com> Date: Thu, 20 Jun 2024 00:05:23 +0800 Subject: [PATCH 297/325] [10.x] Ensure`schema:dump` will dump the migrations table only if it exists (#51827) * fix: migrations table must only be dumped on default connection * fix: migrations table must only be dumped on default connection * Style * Use `$this->connection->getSchemaBuilder()->hasTable()` instead of `Config::get()`` * Update SchemaState.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Schema/MySqlSchemaState.php | 4 +++- src/Illuminate/Database/Schema/PostgresSchemaState.php | 5 ++++- src/Illuminate/Database/Schema/SchemaState.php | 10 ++++++++++ src/Illuminate/Database/Schema/SqliteSchemaState.php | 4 +++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Schema/MySqlSchemaState.php b/src/Illuminate/Database/Schema/MySqlSchemaState.php index 2514c18bd6c1..5bed2f003974 100644 --- a/src/Illuminate/Database/Schema/MySqlSchemaState.php +++ b/src/Illuminate/Database/Schema/MySqlSchemaState.php @@ -26,7 +26,9 @@ public function dump(Connection $connection, $path) $this->removeAutoIncrementingState($path); - $this->appendMigrationData($path); + if ($this->hasMigrationTable()) { + $this->appendMigrationData($path); + } } /** diff --git a/src/Illuminate/Database/Schema/PostgresSchemaState.php b/src/Illuminate/Database/Schema/PostgresSchemaState.php index b3f9361bc92b..70ccd25b5fd3 100644 --- a/src/Illuminate/Database/Schema/PostgresSchemaState.php +++ b/src/Illuminate/Database/Schema/PostgresSchemaState.php @@ -17,9 +17,12 @@ public function dump(Connection $connection, $path) { $commands = collect([ $this->baseDumpCommand().' --schema-only > '.$path, - $this->baseDumpCommand().' -t '.$this->migrationTable.' --data-only >> '.$path, ]); + if ($this->hasMigrationTable()) { + $commands->push($this->baseDumpCommand().' -t '.$this->migrationTable.' --data-only >> '.$path); + } + $commands->map(function ($command, $path) { $this->makeProcess($command)->mustRun($this->output, array_merge($this->baseVariables($this->connection->getConfig()), [ 'LARAVEL_LOAD_PATH' => $path, diff --git a/src/Illuminate/Database/Schema/SchemaState.php b/src/Illuminate/Database/Schema/SchemaState.php index 58d9c3a438aa..b7fa34c168c9 100644 --- a/src/Illuminate/Database/Schema/SchemaState.php +++ b/src/Illuminate/Database/Schema/SchemaState.php @@ -94,6 +94,16 @@ public function makeProcess(...$arguments) return call_user_func($this->processFactory, ...$arguments); } + /** + * Determine if the current connection has a migration table. + * + * @return bool + */ + public function hasMigrationTable(): bool + { + return $this->connection->getSchemaBuilder()->hasTable($this->migrationTable); + } + /** * Specify the name of the application's migration table. * diff --git a/src/Illuminate/Database/Schema/SqliteSchemaState.php b/src/Illuminate/Database/Schema/SqliteSchemaState.php index 10efc7c0aba9..4b66542923e4 100644 --- a/src/Illuminate/Database/Schema/SqliteSchemaState.php +++ b/src/Illuminate/Database/Schema/SqliteSchemaState.php @@ -28,7 +28,9 @@ public function dump(Connection $connection, $path) $this->files->put($path, implode(PHP_EOL, $migrations).PHP_EOL); - $this->appendMigrationData($path); + if ($this->hasMigrationTable()) { + $this->appendMigrationData($path); + } } /** From 27cb4736bb7e60a5311ec73160068dfbcf98336b Mon Sep 17 00:00:00 2001 From: driesvints Date: Fri, 21 Jun 2024 10:06:42 +0000 Subject: [PATCH 298/325] Update version to v10.48.14 --- 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 dfb2f3cd12ae..aab422487c11 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.13'; + const VERSION = '10.48.14'; /** * The base path for the Laravel installation. From 35f07d3d3ceafc748f6301b5f4520dbd15924e36 Mon Sep 17 00:00:00 2001 From: driesvints Date: Fri, 21 Jun 2024 10:08:05 +0000 Subject: [PATCH 299/325] Update CHANGELOG --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8569c1b840ca..624faacde463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.13...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.14...10.x) + +## [v10.48.14](https://github.com/laravel/framework/compare/v10.48.13...v10.48.14) - 2024-06-21 + +* [10.x] Fixes unable to call another command as a initialized instance of `Command` class by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/51824 +* [10.x] fix handle `shift()` on an empty collection by [@Treggats](https://github.com/Treggats) in https://github.com/laravel/framework/pull/51841 +* [10.x] Ensure`schema:dump` will dump the migrations table only if it exists by [@NickSdot](https://github.com/NickSdot) in https://github.com/laravel/framework/pull/51827 ## [v10.48.13](https://github.com/laravel/framework/compare/v10.48.12...v10.48.13) - 2024-06-18 From 57be419dc7471d964e639ce1dcb74a5e8e1f8891 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 2 Jul 2024 10:27:38 +0330 Subject: [PATCH 300/325] [10.x] Set previous exception on `HttpResponseException` (#51986) * set previous exception on `HttpResponseException` * Update src/Illuminate/Http/Exceptions/HttpResponseException.php Co-authored-by: Julius Kiekbusch --------- Co-authored-by: Dries Vints Co-authored-by: Julius Kiekbusch --- src/Illuminate/Http/Exceptions/HttpResponseException.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Exceptions/HttpResponseException.php b/src/Illuminate/Http/Exceptions/HttpResponseException.php index b27052f02c15..c45268680aeb 100644 --- a/src/Illuminate/Http/Exceptions/HttpResponseException.php +++ b/src/Illuminate/Http/Exceptions/HttpResponseException.php @@ -4,6 +4,7 @@ use RuntimeException; use Symfony\Component\HttpFoundation\Response; +use Throwable; class HttpResponseException extends RuntimeException { @@ -18,10 +19,13 @@ class HttpResponseException extends RuntimeException * Create a new HTTP response exception instance. * * @param \Symfony\Component\HttpFoundation\Response $response + * @param \Throwable $previous * @return void */ - public function __construct(Response $response) + public function __construct(Response $response, ?Throwable $previous = null) { + parent::__construct($previous?->getMessage() ?? '', $previous?->getCode() ?? 0, $previous); + $this->response = $response; } From 1723e038c9ec99b434e5bed9c27f044bd50a4ffb Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 2 Jul 2024 18:01:24 +0000 Subject: [PATCH 301/325] Update version to v10.48.15 --- 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 aab422487c11..71255f5eee88 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.14'; + const VERSION = '10.48.15'; /** * The base path for the Laravel installation. From 500d93a008fec1f135352bfd446c7f2177800919 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 2 Jul 2024 18:02:51 +0000 Subject: [PATCH 302/325] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 624faacde463..4f0b1f9c33ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.14...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.15...10.x) + +## [v10.48.15](https://github.com/laravel/framework/compare/v10.48.14...v10.48.15) - 2024-07-02 + +* [10.x] Set previous exception on `HttpResponseException` by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/51986 ## [v10.48.14](https://github.com/laravel/framework/compare/v10.48.13...v10.48.14) - 2024-06-21 From fc70c5e090bf810a173072c061ed62b490c60549 Mon Sep 17 00:00:00 2001 From: paulyoungnb <132525307+paulyoungnb@users.noreply.github.com> Date: Thu, 4 Jul 2024 07:19:27 +1000 Subject: [PATCH 303/325] Fix Http::retry so that throw is respected for call signature Http::retry([1,2], throw: false) (#52002) * Update HttpClientTest.php * Fix PendingRequest.php to deal with tries as an array * Style CI spaces * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Http/Client/PendingRequest.php | 8 +- tests/Http/HttpClientTest.php | 139 ++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Http/Client/PendingRequest.php b/src/Illuminate/Http/Client/PendingRequest.php index 5dbb9dad8da2..7eb8b0e47b6c 100644 --- a/src/Illuminate/Http/Client/PendingRequest.php +++ b/src/Illuminate/Http/Client/PendingRequest.php @@ -911,11 +911,15 @@ public function send(string $method, string $url, array $options = []) $response->throw($this->throwCallback); } - if ($attempt < $this->tries && $shouldRetry) { + $potentialTries = is_array($this->tries) + ? count($this->tries) + 1 + : $this->tries; + + if ($attempt < $potentialTries && $shouldRetry) { $response->throw(); } - if ($this->tries > 1 && $this->retryThrow) { + if ($potentialTries > 1 && $this->retryThrow) { $response->throw(); } } diff --git a/tests/Http/HttpClientTest.php b/tests/Http/HttpClientTest.php index 9904e9165c01..ea204c8c3f02 100644 --- a/tests/Http/HttpClientTest.php +++ b/tests/Http/HttpClientTest.php @@ -1711,6 +1711,28 @@ public function testRequestExceptionIsThrownWhenRetriesExhausted() $this->factory->assertSentCount(2); } + public function testRequestExceptionIsThrownWhenRetriesExhaustedWithBackoffArray() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + $exception = null; + + try { + $this->factory + ->retry([1], 0, null, true) + ->get('http://foo.com/get'); + } catch (RequestException $e) { + $exception = $e; + } + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + + $this->factory->assertSentCount(2); + } + public function testRequestExceptionIsThrownWithoutRetriesIfRetryNotNecessary() { $this->factory->fake([ @@ -1740,6 +1762,35 @@ public function testRequestExceptionIsThrownWithoutRetriesIfRetryNotNecessary() $this->factory->assertSentCount(1); } + public function testRequestExceptionIsThrownWithoutRetriesIfRetryNotNecessaryWithBackoffArray() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 500), + ]); + + $exception = null; + $whenAttempts = 0; + + try { + $this->factory + ->retry([1000, 1000], 1000, function ($exception) use (&$whenAttempts) { + $whenAttempts++; + + return $exception->response->status() === 403; + }, true) + ->get('http://foo.com/get'); + } catch (RequestException $e) { + $exception = $e; + } + + $this->assertNotNull($exception); + $this->assertInstanceOf(RequestException::class, $exception); + + $this->assertSame(1, $whenAttempts); + + $this->factory->assertSentCount(1); + } + public function testRequestExceptionIsNotThrownWhenDisabledAndRetriesExhausted() { $this->factory->fake([ @@ -1755,6 +1806,21 @@ public function testRequestExceptionIsNotThrownWhenDisabledAndRetriesExhausted() $this->factory->assertSentCount(2); } + public function testRequestExceptionIsNotThrownWhenDisabledAndRetriesExhaustedWithBackoffArray() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 403), + ]); + + $response = $this->factory + ->retry([1, 2], throw: false) + ->get('http://foo.com/get'); + + $this->assertTrue($response->failed()); + + $this->factory->assertSentCount(3); + } + public function testRequestExceptionIsNotThrownWithoutRetriesIfRetryNotNecessary() { $this->factory->fake([ @@ -1778,6 +1844,29 @@ public function testRequestExceptionIsNotThrownWithoutRetriesIfRetryNotNecessary $this->factory->assertSentCount(1); } + public function testRequestExceptionIsNotThrownWithoutRetriesIfRetryNotNecessaryWithBackoffArray() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 500), + ]); + + $whenAttempts = 0; + + $response = $this->factory + ->retry([1, 2], 0, function ($exception) use (&$whenAttempts) { + $whenAttempts++; + + return $exception->response->status() === 403; + }, false) + ->get('http://foo.com/get'); + + $this->assertTrue($response->failed()); + + $this->assertSame(1, $whenAttempts); + + $this->factory->assertSentCount(1); + } + public function testRequestCanBeModifiedInRetryCallback() { $this->factory->fake([ @@ -1803,6 +1892,31 @@ public function testRequestCanBeModifiedInRetryCallback() }); } + public function testRequestCanBeModifiedInRetryCallbackWithBackoffArray() + { + $this->factory->fake([ + '*' => $this->factory->sequence() + ->push(['error'], 500) + ->push(['ok'], 200), + ]); + + $response = $this->factory + ->retry([2], when: function ($exception, $request) { + $this->assertInstanceOf(PendingRequest::class, $request); + + $request->withHeaders(['Foo' => 'Bar']); + + return true; + }, throw: false) + ->get('http://foo.com/get'); + + $this->assertTrue($response->successful()); + + $this->factory->assertSent(function (Request $request) { + return $request->hasHeader('Foo') && $request->header('Foo') === ['Bar']; + }); + } + public function testExceptionThrownInRetryCallbackWithoutRetrying() { $this->factory->fake([ @@ -1828,6 +1942,31 @@ public function testExceptionThrownInRetryCallbackWithoutRetrying() $this->factory->assertSentCount(1); } + public function testExceptionThrownInRetryCallbackWithoutRetryingWithBackoffArray() + { + $this->factory->fake([ + '*' => $this->factory->response(['error'], 500), + ]); + + $exception = null; + + try { + $this->factory + ->retry([1, 2, 3], when: function ($exception) use (&$whenAttempts) { + throw new Exception('Foo bar'); + }, throw: false) + ->get('http://foo.com/get'); + } catch (Exception $e) { + $exception = $e; + } + + $this->assertNotNull($exception); + $this->assertInstanceOf(Exception::class, $exception); + $this->assertEquals('Foo bar', $exception->getMessage()); + + $this->factory->assertSentCount(1); + } + public function testRequestsWillBeWaitingSleepMillisecondsReceivedBeforeRetry() { Sleep::fake(); From 6230d5691ea9cab4cafb07717efcc58f703ba5a6 Mon Sep 17 00:00:00 2001 From: SUNAOKA Norifumi <105845+sunaoka@users.noreply.github.com> Date: Thu, 4 Jul 2024 06:35:00 +0900 Subject: [PATCH 304/325] Set application_name and character set as DSN string. (#51985) --- .../Database/Connectors/PostgresConnector.php | 50 ++++--------------- tests/Database/DatabaseConnectorTest.php | 23 ++++----- 2 files changed, 21 insertions(+), 52 deletions(-) diff --git a/src/Illuminate/Database/Connectors/PostgresConnector.php b/src/Illuminate/Database/Connectors/PostgresConnector.php index 5ec0e70437d1..9834d2ce639f 100755 --- a/src/Illuminate/Database/Connectors/PostgresConnector.php +++ b/src/Illuminate/Database/Connectors/PostgresConnector.php @@ -38,8 +38,6 @@ public function connect(array $config) $this->configureIsolationLevel($connection, $config); - $this->configureEncoding($connection, $config); - // Next, we will check to see if a timezone has been specified in this config // and if it has we will issue a statement to modify the timezone with the // database. Setting this DB timezone is an optional configuration item. @@ -47,11 +45,6 @@ public function connect(array $config) $this->configureSearchPath($connection, $config); - // Postgres allows an application_name to be set by the user and this name is - // used to when monitoring the application with pg_stat_activity. So we'll - // determine if the option has been specified and run a statement if so. - $this->configureApplicationName($connection, $config); - $this->configureSynchronousCommit($connection, $config); return $connection; @@ -71,22 +64,6 @@ protected function configureIsolationLevel($connection, array $config) } } - /** - * Set the connection character set and collation. - * - * @param \PDO $connection - * @param array $config - * @return void - */ - protected function configureEncoding($connection, $config) - { - if (! isset($config['charset'])) { - return; - } - - $connection->prepare("set names '{$config['charset']}'")->execute(); - } - /** * Set the timezone on the connection. * @@ -132,22 +109,6 @@ protected function quoteSearchPath($searchPath) return count($searchPath) === 1 ? '"'.$searchPath[0].'"' : '"'.implode('", "', $searchPath).'"'; } - /** - * Set the application name on the connection. - * - * @param \PDO $connection - * @param array $config - * @return void - */ - protected function configureApplicationName($connection, $config) - { - if (isset($config['application_name'])) { - $applicationName = $config['application_name']; - - $connection->prepare("set application_name to '$applicationName'")->execute(); - } - } - /** * Create a DSN string from a configuration. * @@ -178,6 +139,17 @@ protected function getDsn(array $config) $dsn .= ";port={$port}"; } + if (isset($charset)) { + $dsn .= ";client_encoding='{$charset}'"; + } + + // Postgres allows an application_name to be set by the user and this name is + // used to when monitoring the application with pg_stat_activity. So we'll + // determine if the option has been specified and run a statement if so. + if (isset($application_name)) { + $dsn .= ";application_name='".str_replace("'", "\'", $application_name)."'"; + } + return $this->addSslOptions($dsn, $config); } diff --git a/tests/Database/DatabaseConnectorTest.php b/tests/Database/DatabaseConnectorTest.php index 79ee6d0606c4..7990ea285858 100755 --- a/tests/Database/DatabaseConnectorTest.php +++ b/tests/Database/DatabaseConnectorTest.php @@ -75,15 +75,15 @@ public function testMySqlConnectCallsCreateConnectionWithIsolationLevel() public function testPostgresConnectCallsCreateConnectionWithProperArguments() { - $dsn = 'pgsql:host=foo;dbname=\'bar\';port=111'; + $dsn = 'pgsql:host=foo;dbname=\'bar\';port=111;client_encoding=\'utf8\''; $config = ['host' => 'foo', 'database' => 'bar', 'port' => 111, 'charset' => 'utf8']; $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock(); $connection = m::mock(stdClass::class); $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']); $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection); $statement = m::mock(PDOStatement::class); - $connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement); - $statement->shouldReceive('execute')->once(); + $connection->shouldReceive('prepare')->zeroOrMoreTimes()->andReturn($statement); + $statement->shouldReceive('execute')->zeroOrMoreTimes(); $result = $connector->connect($config); $this->assertSame($result, $connection); @@ -97,16 +97,15 @@ public function testPostgresConnectCallsCreateConnectionWithProperArguments() */ public function testPostgresSearchPathIsSet($searchPath, $expectedSql) { - $dsn = 'pgsql:host=foo;dbname=\'bar\''; + $dsn = 'pgsql:host=foo;dbname=\'bar\';client_encoding=\'utf8\''; $config = ['host' => 'foo', 'database' => 'bar', 'search_path' => $searchPath, 'charset' => 'utf8']; $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock(); $connection = m::mock(stdClass::class); $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']); $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection); $statement = m::mock(PDOStatement::class); - $connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement); $connection->shouldReceive('prepare')->once()->with($expectedSql)->andReturn($statement); - $statement->shouldReceive('execute')->twice(); + $statement->shouldReceive('execute')->once(); $result = $connector->connect($config); $this->assertSame($result, $connection); @@ -184,16 +183,15 @@ public static function provideSearchPaths() public function testPostgresSearchPathFallbackToConfigKeySchema() { - $dsn = 'pgsql:host=foo;dbname=\'bar\''; + $dsn = 'pgsql:host=foo;dbname=\'bar\';client_encoding=\'utf8\''; $config = ['host' => 'foo', 'database' => 'bar', 'schema' => ['public', '"user"'], 'charset' => 'utf8']; $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock(); $connection = m::mock(stdClass::class); $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']); $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection); $statement = m::mock(PDOStatement::class); - $connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement); $connection->shouldReceive('prepare')->once()->with('set search_path to "public", "user"')->andReturn($statement); - $statement->shouldReceive('execute')->twice(); + $statement->shouldReceive('execute')->once(); $result = $connector->connect($config); $this->assertSame($result, $connection); @@ -201,16 +199,15 @@ public function testPostgresSearchPathFallbackToConfigKeySchema() public function testPostgresApplicationNameIsSet() { - $dsn = 'pgsql:host=foo;dbname=\'bar\''; + $dsn = 'pgsql:host=foo;dbname=\'bar\';client_encoding=\'utf8\';application_name=\'Laravel App\''; $config = ['host' => 'foo', 'database' => 'bar', 'charset' => 'utf8', 'application_name' => 'Laravel App']; $connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock(); $connection = m::mock(stdClass::class); $connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']); $connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection); $statement = m::mock(PDOStatement::class); - $connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement); - $connection->shouldReceive('prepare')->once()->with('set application_name to \'Laravel App\'')->andReturn($statement); - $statement->shouldReceive('execute')->twice(); + $connection->shouldReceive('prepare')->zeroOrMoreTimes()->andReturn($statement); + $statement->shouldReceive('execute')->zeroOrMoreTimes(); $result = $connector->connect($config); $this->assertSame($result, $connection); From f9a3a50fae399d75e125b0eea637dda90c99eae7 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 9 Jul 2024 15:25:22 +0000 Subject: [PATCH 305/325] Update version to v10.48.16 --- 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 71255f5eee88..5c3c6a730c88 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.15'; + const VERSION = '10.48.16'; /** * The base path for the Laravel installation. From e052cb83b0bbd73c0359d1ab3770c0d0a1831f23 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 9 Jul 2024 15:26:51 +0000 Subject: [PATCH 306/325] Update CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f0b1f9c33ca..188289e951fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.15...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.16...10.x) + +## [v10.48.16](https://github.com/laravel/framework/compare/v10.48.15...v10.48.16) - 2024-07-09 + +* [10.x] Fix Http::retry so that throw is respected for call signature Http::retry([1,2], throw: false) by [@paulyoungnb](https://github.com/paulyoungnb) in https://github.com/laravel/framework/pull/52002 +* [10.x] Set application_name and character set as PostgreSQL DSN string by [@sunaoka](https://github.com/sunaoka) in https://github.com/laravel/framework/pull/51985 ## [v10.48.15](https://github.com/laravel/framework/compare/v10.48.14...v10.48.15) - 2024-07-02 From 96712ba63e37b54ee8d0738eb613e13c9aff522f Mon Sep 17 00:00:00 2001 From: Alfred Date: Thu, 11 Jul 2024 23:41:20 +0200 Subject: [PATCH 307/325] Fix PHP_CLI_SERVER_WORKERS warning by applying the same fix as 11.x (#52094) --- src/Illuminate/Foundation/Console/ServeCommand.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Console/ServeCommand.php b/src/Illuminate/Foundation/Console/ServeCommand.php index a73bf52ef5c1..df830148c7f7 100644 --- a/src/Illuminate/Foundation/Console/ServeCommand.php +++ b/src/Illuminate/Foundation/Console/ServeCommand.php @@ -295,7 +295,7 @@ protected function handleProcessOutput() $this->output->write(' '.str_repeat('.', $dots)); $this->output->writeln(" ~ {$runTime}s"); - } elseif (str($line)->contains(['Closed without sending a request'])) { + } elseif (str($line)->contains(['Closed without sending a request', 'Failed to poll event'])) { // ... } elseif (! empty($line)) { $position = strpos($line, '] '); From 7692ec4456636f84092709fb7c5b38d22e4e4609 Mon Sep 17 00:00:00 2001 From: Graham Campbell Date: Mon, 22 Jul 2024 15:24:00 +0100 Subject: [PATCH 308/325] Backport #51615 (#52215) Co-authored-by: Hafez Divandari --- .../Database/Schema/Grammars/SQLiteGrammar.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 201121cb6170..d3dfdebb6392 100755 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -123,7 +123,7 @@ public function compileColumns($table) return sprintf( 'select name, type, not "notnull" as "nullable", dflt_value as "default", pk as "primary" ' .'from pragma_table_info(%s) order by cid asc', - $this->wrap(str_replace('.', '__', $table)) + $this->quoteString(str_replace('.', '__', $table)) ); } @@ -136,12 +136,12 @@ public function compileColumns($table) public function compileIndexes($table) { return sprintf( - 'select "primary" as name, group_concat(col) as columns, 1 as "unique", 1 as "primary" ' + 'select \'primary\' as name, group_concat(col) as columns, 1 as "unique", 1 as "primary" ' .'from (select name as col from pragma_table_info(%s) where pk > 0 order by pk, cid) group by name ' - .'union select name, group_concat(col) as columns, "unique", origin = "pk" as "primary" ' + .'union select name, group_concat(col) as columns, "unique", origin = \'pk\' as "primary" ' .'from (select il.*, ii.name as col from pragma_index_list(%s) il, pragma_index_info(il.name) ii order by il.seq, ii.seqno) ' .'group by name, "unique", "primary"', - $table = $this->wrap(str_replace('.', '__', $table)), + $table = $this->quoteString(str_replace('.', '__', $table)), $table ); } @@ -159,7 +159,7 @@ public function compileForeignKeys($table) .'group_concat("to") as foreign_columns, on_update, on_delete ' .'from (select * from pragma_foreign_key_list(%s) order by id desc, seq) ' .'group by id, "table", on_update, on_delete', - $this->wrap(str_replace('.', '__', $table)) + $this->quoteString(str_replace('.', '__', $table)) ); } From 60f3c8f667b24a09e0392e26b1f40fb9067cdc3c Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 23 Jul 2024 16:06:06 +0000 Subject: [PATCH 309/325] Update version to v10.48.17 --- 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 5c3c6a730c88..a54b497ce276 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.16'; + const VERSION = '10.48.17'; /** * The base path for the Laravel installation. From c278f6ccb8ac3b25dbe41c89a40e6416a16b96b4 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 23 Jul 2024 16:07:49 +0000 Subject: [PATCH 310/325] Update CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 188289e951fd..10e5e28da8cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.16...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.17...10.x) + +## [v10.48.17](https://github.com/laravel/framework/compare/v10.48.16...v10.48.17) - 2024-07-23 + +* [10.x] Fix PHP_CLI_SERVER_WORKERS warning by suppressing it by [@pelomedusa](https://github.com/pelomedusa) in https://github.com/laravel/framework/pull/52094 +* [10.x] Backport #51615 by [@GrahamCampbell](https://github.com/GrahamCampbell) in https://github.com/laravel/framework/pull/52215 ## [v10.48.16](https://github.com/laravel/framework/compare/v10.48.15...v10.48.16) - 2024-07-09 From 82e9dbce223dbbd343dbfd3081f227155a40153c Mon Sep 17 00:00:00 2001 From: Caleb White Date: Mon, 29 Jul 2024 01:54:36 -0500 Subject: [PATCH 311/325] fix: Model/JsonResource::toJson should not fail with prior json errors (#52293) --- src/Illuminate/Database/Eloquent/Model.php | 9 ++++--- .../Http/Resources/Json/JsonResource.php | 9 ++++--- tests/Database/DatabaseEloquentModelTest.php | 11 ++++++++ tests/Http/JsonResourceTest.php | 27 +++++++++++++++++++ 4 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 tests/Http/JsonResourceTest.php diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index ee4c10aae4c4..672939b95c4c 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -20,6 +20,7 @@ use Illuminate\Support\Collection as BaseCollection; use Illuminate\Support\Str; use Illuminate\Support\Traits\ForwardsCalls; +use JsonException; use JsonSerializable; use LogicException; @@ -1646,10 +1647,10 @@ public function toArray() */ public function toJson($options = 0) { - $json = json_encode($this->jsonSerialize(), $options); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw JsonEncodingException::forModel($this, json_last_error_msg()); + try { + $json = json_encode($this->jsonSerialize(), $options | JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw JsonEncodingException::forModel($this, $e->getMessage()); } return $json; diff --git a/src/Illuminate/Http/Resources/Json/JsonResource.php b/src/Illuminate/Http/Resources/Json/JsonResource.php index 0c2fd151ffbe..cf08343d7832 100644 --- a/src/Illuminate/Http/Resources/Json/JsonResource.php +++ b/src/Illuminate/Http/Resources/Json/JsonResource.php @@ -12,6 +12,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Resources\ConditionallyLoadsAttributes; use Illuminate\Http\Resources\DelegatesToResource; +use JsonException; use JsonSerializable; class JsonResource implements ArrayAccess, JsonSerializable, Responsable, UrlRoutable @@ -144,10 +145,10 @@ public function toArray(Request $request) */ public function toJson($options = 0) { - $json = json_encode($this->jsonSerialize(), $options); - - if (json_last_error() !== JSON_ERROR_NONE) { - throw JsonEncodingException::forResource($this, json_last_error_msg()); + try { + $json = json_encode($this->jsonSerialize(), $options | JSON_THROW_ON_ERROR); + } catch (JsonException $e) { + throw JsonEncodingException::forResource($this, $e->getMessage()); } return $json; diff --git a/tests/Database/DatabaseEloquentModelTest.php b/tests/Database/DatabaseEloquentModelTest.php index 1966e5550bee..15b248c5c622 100755 --- a/tests/Database/DatabaseEloquentModelTest.php +++ b/tests/Database/DatabaseEloquentModelTest.php @@ -2860,6 +2860,17 @@ public function testDiscardChanges() $this->assertNull($user->getOriginal('name')); $this->assertNull($user->getAttribute('name')); } + + public function testModelToJsonSucceedsWithPriorErrors(): void + { + $user = new EloquentModelStub(['name' => 'Mateus']); + + // Simulate a JSON error + json_decode('{'); + $this->assertTrue(json_last_error() !== JSON_ERROR_NONE); + + $this->assertSame('{"name":"Mateus"}', $user->toJson(JSON_THROW_ON_ERROR)); + } } class EloquentTestObserverStub diff --git a/tests/Http/JsonResourceTest.php b/tests/Http/JsonResourceTest.php new file mode 100644 index 000000000000..256c40aea991 --- /dev/null +++ b/tests/Http/JsonResourceTest.php @@ -0,0 +1,27 @@ + $model]) + ->makePartial() + ->shouldReceive('jsonSerialize')->once()->andReturn(['foo' => 'bar']) + ->getMock(); + + // Simulate a JSON error + json_decode('{'); + $this->assertTrue(json_last_error() !== JSON_ERROR_NONE); + + $this->assertSame('{"foo":"bar"}', $resource->toJson(JSON_THROW_ON_ERROR)); + } +} From 8937e25b0ca2bac36775432a2240e7238d746b04 Mon Sep 17 00:00:00 2001 From: Chin Leung Date: Tue, 30 Jul 2024 02:53:27 -0400 Subject: [PATCH 312/325] [10.x] Fix runPaginationCountQuery not working properly for union queries (#52314) * Fix runPaginationCountQuery not working properly for union queries * Added tests --- src/Illuminate/Database/Query/Builder.php | 4 +-- tests/Database/DatabaseQueryBuilderTest.php | 28 +++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index b9bacd696082..9f87562f01d6 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -3016,10 +3016,10 @@ protected function runPaginationCountQuery($columns = ['*']) ->get()->all(); } - $without = $this->unions ? ['orders', 'limit', 'offset'] : ['columns', 'orders', 'limit', 'offset']; + $without = $this->unions ? ['unionOrders', 'unionLimit', 'unionOffset'] : ['columns', 'orders', 'limit', 'offset']; return $this->cloneWithout($without) - ->cloneWithoutBindings($this->unions ? ['order'] : ['select', 'order']) + ->cloneWithoutBindings($this->unions ? ['unionOrder'] : ['select', 'order']) ->setAggregate('count', $this->withoutSelectAliases($columns)) ->get()->all(); } diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index 39f49f45d53c..abd2fcc2930c 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -2070,6 +2070,34 @@ public function testGetCountForPaginationWithUnion() $this->assertEquals(1, $count); } + public function testGetCountForPaginationWithUnionOrders() + { + $builder = $this->getBuilder(); + $builder->from('posts')->select('id')->union($this->getBuilder()->from('videos')->select('id'))->latest(); + + $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from ((select "id" from "posts") union (select "id" from "videos")) as "temp_table"', [], true)->andReturn([['aggregate' => 1]]); + $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function ($builder, $results) { + return $results; + }); + + $count = $builder->getCountForPagination(); + $this->assertEquals(1, $count); + } + + public function testGetCountForPaginationWithUnionLimitAndOffset() + { + $builder = $this->getBuilder(); + $builder->from('posts')->select('id')->union($this->getBuilder()->from('videos')->select('id'))->take(15)->skip(1); + + $builder->getConnection()->shouldReceive('select')->once()->with('select count(*) as aggregate from ((select "id" from "posts") union (select "id" from "videos")) as "temp_table"', [], true)->andReturn([['aggregate' => 1]]); + $builder->getProcessor()->shouldReceive('processSelect')->once()->andReturnUsing(function ($builder, $results) { + return $results; + }); + + $count = $builder->getCountForPagination(); + $this->assertEquals(1, $count); + } + public function testWhereShortcut() { $builder = $this->getBuilder(); From d9729d476c3efe79f950ebcb6de1ec8199a421e6 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 30 Jul 2024 15:05:11 +0000 Subject: [PATCH 313/325] Update version to v10.48.18 --- 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 a54b497ce276..a75f1c12d4d6 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.17'; + const VERSION = '10.48.18'; /** * The base path for the Laravel installation. From 639a2ee9ba16e5cff0a20fbe348c69487e681506 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 30 Jul 2024 15:06:56 +0000 Subject: [PATCH 314/325] Update CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10e5e28da8cb..bf5f1042bd2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.17...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.18...10.x) + +## [v10.48.18](https://github.com/laravel/framework/compare/v10.48.17...v10.48.18) - 2024-07-30 + +* [10.x] backport #52188 by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52293 +* [10.x] Fix runPaginationCountQuery not working properly for union queries by [@chinleung](https://github.com/chinleung) in https://github.com/laravel/framework/pull/52314 ## [v10.48.17](https://github.com/laravel/framework/compare/v10.48.16...v10.48.17) - 2024-07-23 From 08b1b98ea649dfb4c25b95cf30893344d933165e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20JEAN?= Date: Thu, 1 Aug 2024 15:19:19 +0200 Subject: [PATCH 315/325] Add compatible query type to `Model::resolveRouteBindingQuery` (#52339) * Add compatible query type to Model::resolveRouteBindingQuery * Update Model.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Database/Eloquent/Model.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Model.php b/src/Illuminate/Database/Eloquent/Model.php index 672939b95c4c..48618cbbd61f 100644 --- a/src/Illuminate/Database/Eloquent/Model.php +++ b/src/Illuminate/Database/Eloquent/Model.php @@ -2123,7 +2123,7 @@ protected function childRouteBindingRelationshipName($childType) /** * Retrieve the model for a bound value. * - * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Relations\Relation $query + * @param \Illuminate\Database\Eloquent\Model|\Illuminate\Contracts\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query * @param mixed $value * @param string|null $field * @return \Illuminate\Database\Eloquent\Relations\Relation From db768e41789bb243bdd94c433dec79f073150744 Mon Sep 17 00:00:00 2001 From: Fabien Villepinte Date: Thu, 1 Aug 2024 15:19:52 +0200 Subject: [PATCH 316/325] [10.x] Fix callable argument type (#52335) --- src/Illuminate/Database/Eloquent/Factories/Factory.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Factories/Factory.php b/src/Illuminate/Database/Eloquent/Factories/Factory.php index d9a453359d23..63df5b8363ec 100644 --- a/src/Illuminate/Database/Eloquent/Factories/Factory.php +++ b/src/Illuminate/Database/Eloquent/Factories/Factory.php @@ -676,7 +676,7 @@ public function afterMaking(Closure $callback) /** * Add a new "after creating" callback to the model definition. * - * @param \Closure(\Illuminate\Database\Eloquent\Model|TModel): mixed $callback + * @param \Closure(\Illuminate\Database\Eloquent\Model|TModel, \Illuminate\Database\Eloquent\Model|null): mixed $callback * @return static */ public function afterCreating(Closure $callback) From e1e7cd18a3c3b64c1ac2eb8a1359e173884b081d Mon Sep 17 00:00:00 2001 From: Caleb White Date: Mon, 5 Aug 2024 12:20:47 -0500 Subject: [PATCH 317/325] fix: Request::json() json errors when decoding empty string (#52389) --- src/Illuminate/Http/Request.php | 2 +- tests/Http/HttpRequestTest.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Request.php b/src/Illuminate/Http/Request.php index 1a36ec2d0f1e..d7c8745f719b 100644 --- a/src/Illuminate/Http/Request.php +++ b/src/Illuminate/Http/Request.php @@ -404,7 +404,7 @@ public function get(string $key, mixed $default = null): mixed public function json($key = null, $default = null) { if (! isset($this->json)) { - $this->json = new InputBag((array) json_decode($this->getContent(), true)); + $this->json = new InputBag((array) json_decode($this->getContent() ?: '[]', true)); } if (is_null($key)) { diff --git a/tests/Http/HttpRequestTest.php b/tests/Http/HttpRequestTest.php index 75a0cf5571af..b0d3f53e1242 100644 --- a/tests/Http/HttpRequestTest.php +++ b/tests/Http/HttpRequestTest.php @@ -1608,4 +1608,14 @@ public function testItCanHaveObjectsInJsonPayload() $this->assertSame(['name' => 'Laravel'], $request->get('framework')); } + + public function testItDoesNotGenerateJsonErrorsForEmptyContent() + { + // clear any existing errors + json_encode(null); + + Request::create('', 'GET')->json(); + + $this->assertTrue(json_last_error() === JSON_ERROR_NONE); + } } From d984d0ddc14cb7cc62741681518cd9388db9e1f5 Mon Sep 17 00:00:00 2001 From: James Funk Date: Mon, 5 Aug 2024 10:41:40 -0600 Subject: [PATCH 318/325] In MySQL, harvest last insert ID immediately after query is executed --- src/Illuminate/Database/MySqlConnection.php | 41 +++++++++++++++++++ .../Query/Processors/MySqlProcessor.php | 20 +++++++++ .../MySql/DatabaseMySqlConnectionTest.php | 24 +++++++++++ 3 files changed, 85 insertions(+) diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index 460a4fd375c1..502c6b62782c 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -14,6 +14,47 @@ class MySqlConnection extends Connection { + /** + * The last inserted ID generated by the server + * + * @var string|int|null + */ + protected $lastInsertId; + + /** + * Run an insert statement against the database. + * + * @param string $query + * @param array $bindings + * @param string|null $sequence + * @return bool + */ + public function insert($query, $bindings = [], $sequence = null) + { + return $this->run($query, $bindings, function ($query, $bindings) use ($sequence) { + if ($this->pretending()) { + return true; + } + + $statement = $this->getPdo()->prepare($query); + + $this->bindValues($statement, $this->prepareBindings($bindings)); + + $this->recordsHaveBeenModified(); + + $result = $statement->execute(); + + $this->lastInsertId = $this->getPdo()->lastInsertId($sequence); + + return $result; + }); + } + + public function getLastInsertId() + { + return $this->lastInsertId; + } + /** * Escape a binary value for safe SQL embedding. * diff --git a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php index 07553c39195b..cfbbdc3cec7b 100644 --- a/src/Illuminate/Database/Query/Processors/MySqlProcessor.php +++ b/src/Illuminate/Database/Query/Processors/MySqlProcessor.php @@ -2,6 +2,8 @@ namespace Illuminate\Database\Query\Processors; +use Illuminate\Database\Query\Builder; + class MySqlProcessor extends Processor { /** @@ -19,6 +21,24 @@ public function processColumnListing($results) }, $results); } + /** + * Process an "insert get ID" query. + * + * @param \Illuminate\Database\Query\Builder $query + * @param string $sql + * @param array $values + * @param string|null $sequence + * @return int + */ + public function processInsertGetId(Builder $query, $sql, $values, $sequence = null) + { + $query->getConnection()->insert($sql, $values, $sequence); + + $id = $query->getConnection()->getLastInsertId(); + + return is_numeric($id) ? (int) $id : $id; + } + /** * Process the results of a columns query. * diff --git a/tests/Integration/Database/MySql/DatabaseMySqlConnectionTest.php b/tests/Integration/Database/MySql/DatabaseMySqlConnectionTest.php index 16b067eac3ca..4f5df6d24f74 100644 --- a/tests/Integration/Database/MySql/DatabaseMySqlConnectionTest.php +++ b/tests/Integration/Database/MySql/DatabaseMySqlConnectionTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Integration\Database\MySql; +use Illuminate\Database\Events\QueryExecuted; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -151,4 +152,27 @@ public static function jsonContainsKeyDataProvider() 'null value' => [1, 'json_col->bar'], ]; } + + public function testLastInsertIdIsPreserved() + { + if (! Schema::hasTable('auto_id_table')) { + Schema::create('auto_id_table', function (Blueprint $table) { + $table->id(); + }); + } + + try { + static $callbackExecuted = false; + DB::listen(function (QueryExecuted $event) use (&$callbackExecuted) { + DB::getPdo()->query('SELECT 1'); + $callbackExecuted = true; + }); + + $id = DB::table('auto_id_table')->insertGetId([]); + $this->assertTrue($callbackExecuted, 'The query listener was not executed.'); + $this->assertEquals(1, $id); + } finally { + Schema::drop('auto_id_table'); + } + } } From 16ac9c51279ccb7f411eab5c79c15f390524546b Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Tue, 6 Aug 2024 09:00:46 -0500 Subject: [PATCH 319/325] formatting --- src/Illuminate/Database/MySqlConnection.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index 502c6b62782c..3a2df883c0b4 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -50,11 +50,6 @@ public function insert($query, $bindings = [], $sequence = null) }); } - public function getLastInsertId() - { - return $this->lastInsertId; - } - /** * Escape a binary value for safe SQL embedding. * @@ -79,6 +74,16 @@ protected function isUniqueConstraintError(Exception $exception) return boolval(preg_match('#Integrity constraint violation: 1062#i', $exception->getMessage())); } + /** + * Get the connection's last insert ID. + * + * @return string|int|null + */ + public function getLastInsertId() + { + return $this->lastInsertId; + } + /** * Determine if the connected database is a MariaDB database. * From d2546d52254abe0f894e0a1658ac779bc6364e99 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 6 Aug 2024 14:01:22 +0000 Subject: [PATCH 320/325] Apply fixes from StyleCI --- src/Illuminate/Database/MySqlConnection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/MySqlConnection.php b/src/Illuminate/Database/MySqlConnection.php index 3a2df883c0b4..9db946ab11e0 100755 --- a/src/Illuminate/Database/MySqlConnection.php +++ b/src/Illuminate/Database/MySqlConnection.php @@ -15,7 +15,7 @@ class MySqlConnection extends Connection { /** - * The last inserted ID generated by the server + * The last inserted ID generated by the server. * * @var string|int|null */ From d816681a99a8fe2ea42fdf793b401dd3b34775a7 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 6 Aug 2024 14:06:43 +0000 Subject: [PATCH 321/325] Update version to v10.48.19 --- 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 a75f1c12d4d6..c2e22a0a7743 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.18'; + const VERSION = '10.48.19'; /** * The base path for the Laravel installation. From 763b942466f310cb83b711bf9cd243c5b161cd07 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 6 Aug 2024 14:08:33 +0000 Subject: [PATCH 322/325] Update CHANGELOG --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bf5f1042bd2a..69942080c4ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.18...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.19...10.x) + +## [v10.48.19](https://github.com/laravel/framework/compare/v10.48.18...v10.48.19) - 2024-08-06 + +* Add compatible query type to `Model::resolveRouteBindingQuery` by [@sebj54](https://github.com/sebj54) in https://github.com/laravel/framework/pull/52339 +* [10.x] Fix `Factory::afterCreating` callable argument type by [@villfa](https://github.com/villfa) in https://github.com/laravel/framework/pull/52335 +* [10.x] backport #52204 by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52389 +* [10.x] In MySQL, harvest last insert ID immediately after query is executed by [@piurafunk](https://github.com/piurafunk) in https://github.com/laravel/framework/pull/52390 ## [v10.48.18](https://github.com/laravel/framework/compare/v10.48.17...v10.48.18) - 2024-07-30 From 0138d780cb6ba829ab6e312b593a31d1d091736c Mon Sep 17 00:00:00 2001 From: Caleb White Date: Wed, 7 Aug 2024 16:07:46 -0500 Subject: [PATCH 323/325] fix: prevent casting empty string to array from triggering json error (#52415) --- .../Eloquent/Concerns/HasAttributes.php | 6 +++- .../DatabaseConcernsHasAttributesTest.php | 28 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 991f06434a5b..c2c478d1cb59 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1271,7 +1271,11 @@ protected function asJson($value) */ public function fromJson($value, $asObject = false) { - return Json::decode($value ?? '', ! $asObject); + if ($value === null || $value === '') { + return null; + } + + return Json::decode($value, ! $asObject); } /** diff --git a/tests/Database/DatabaseConcernsHasAttributesTest.php b/tests/Database/DatabaseConcernsHasAttributesTest.php index 2e2159c4c102..f4e31d4122a4 100644 --- a/tests/Database/DatabaseConcernsHasAttributesTest.php +++ b/tests/Database/DatabaseConcernsHasAttributesTest.php @@ -21,6 +21,14 @@ public function testWithConstructorArguments() $attributes = $instance->getMutatedAttributes(); $this->assertEquals(['some_attribute'], $attributes); } + + public function testCastingEmptyStringToArrayDoesNotError() + { + $instance = new HasAttributesWithArrayCast(); + $this->assertEquals(['foo' => null], $instance->attributesToArray()); + + $this->assertTrue(json_last_error() === JSON_ERROR_NONE); + } } class HasAttributesWithoutConstructor @@ -40,3 +48,23 @@ public function __construct($someValue) { } } + +class HasAttributesWithArrayCast +{ + use HasAttributes; + + public function getArrayableAttributes(): array + { + return ['foo' => '']; + } + + public function getCasts(): array + { + return ['foo' => 'array']; + } + + public function usesTimestamps(): bool + { + return false; + } +} From be2be342d4c74db6a8d2bd18469cd6d488ab9c98 Mon Sep 17 00:00:00 2001 From: driesvints Date: Fri, 9 Aug 2024 07:55:45 +0000 Subject: [PATCH 324/325] Update version to v10.48.20 --- 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 c2e22a0a7743..b9bdd713422a 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -40,7 +40,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '10.48.19'; + const VERSION = '10.48.20'; /** * The base path for the Laravel installation. From 05a9554ac0c7361504a54e350787aba84d028ce7 Mon Sep 17 00:00:00 2001 From: driesvints Date: Fri, 9 Aug 2024 07:57:21 +0000 Subject: [PATCH 325/325] Update CHANGELOG --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69942080c4ee..c1721828ee90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Release Notes for 10.x -## [Unreleased](https://github.com/laravel/framework/compare/v10.48.19...10.x) +## [Unreleased](https://github.com/laravel/framework/compare/v10.48.20...10.x) + +## [v10.48.20](https://github.com/laravel/framework/compare/v10.48.19...v10.48.20) - 2024-08-09 + +* [10.x] fix: prevent casting empty string to array from triggering json error by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52415 ## [v10.48.19](https://github.com/laravel/framework/compare/v10.48.18...v10.48.19) - 2024-08-06