From e443437a10f455162c0e5afeaeb4ed429f10e7a2 Mon Sep 17 00:00:00 2001 From: SUNAOKA Norifumi <105845+sunaoka@users.noreply.github.com> Date: Tue, 3 Sep 2024 22:20:49 +0900 Subject: [PATCH 001/100] Fix docblock for Filesystem::hash() (#52630) --- 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 cfd4c2207835..8bba99a2f2ed 100644 --- a/src/Illuminate/Filesystem/Filesystem.php +++ b/src/Illuminate/Filesystem/Filesystem.php @@ -184,7 +184,7 @@ public function lines($path) * * @param string $path * @param string $algorithm - * @return string + * @return string|false */ public function hash($path, $algorithm = 'md5') { From 57249c3798da32c3caee7cc1632a56b100dfd2bc Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Tue, 3 Sep 2024 13:21:20 +0000 Subject: [PATCH 002/100] 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 6f15962587d1..2ef76942a4ee 100755 --- a/src/Illuminate/Support/Facades/File.php +++ b/src/Illuminate/Support/Facades/File.php @@ -11,7 +11,7 @@ * @method static mixed getRequire(string $path, array $data = []) * @method static mixed requireOnce(string $path, array $data = []) * @method static \Illuminate\Support\LazyCollection lines(string $path) - * @method static string hash(string $path, string $algorithm = 'md5') + * @method static string|false hash(string $path, string $algorithm = 'md5') * @method static int|bool put(string $path, string $contents, bool $lock = false) * @method static void replace(string $path, string $content, int|null $mode = null) * @method static void replaceInFile(array|string $search, array|string $replace, string $path) From 4153267b88a1e552001a0bde145526f9e561f3b9 Mon Sep 17 00:00:00 2001 From: Nauman Javed <63299036+nomitoor@users.noreply.github.com> Date: Tue, 3 Sep 2024 18:30:38 +0500 Subject: [PATCH 003/100] Fix Apostrophe Handling in SeeInOrder.php and Enhance Test Coverage (#52627) * Update SeeInOrder.php The issue is happening when in the in order array, one of the string has apostrophe on it. So this PR will resolve that issue. * Update MailMailableAssertionsTest.php Updated tests for apostrophe * Update SeeInOrder.php --------- Co-authored-by: Taylor Otwell --- .../Testing/Constraints/SeeInOrder.php | 8 +- tests/Mail/MailMailableAssertionsTest.php | 88 +++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Testing/Constraints/SeeInOrder.php b/src/Illuminate/Testing/Constraints/SeeInOrder.php index 609f32d50b92..aba5c6bdac4c 100644 --- a/src/Illuminate/Testing/Constraints/SeeInOrder.php +++ b/src/Illuminate/Testing/Constraints/SeeInOrder.php @@ -40,6 +40,8 @@ public function __construct($content) */ public function matches($values): bool { + $decodedContent = html_entity_decode($this->content, ENT_QUOTES, 'UTF-8'); + $position = 0; foreach ($values as $value) { @@ -47,7 +49,9 @@ public function matches($values): bool continue; } - $valuePosition = mb_strpos($this->content, $value, $position); + $decodedValue = html_entity_decode($value, ENT_QUOTES, 'UTF-8'); + + $valuePosition = mb_strpos($decodedContent, $decodedValue, $position); if ($valuePosition === false || $valuePosition < $position) { $this->failedValue = $value; @@ -55,7 +59,7 @@ public function matches($values): bool return false; } - $position = $valuePosition + mb_strlen($value); + $position = $valuePosition + mb_strlen($decodedValue); } return true; diff --git a/tests/Mail/MailMailableAssertionsTest.php b/tests/Mail/MailMailableAssertionsTest.php index 536a9953109b..1584fb84e6f5 100644 --- a/tests/Mail/MailMailableAssertionsTest.php +++ b/tests/Mail/MailMailableAssertionsTest.php @@ -136,6 +136,92 @@ public function testMailableAssertInOrderHtmlFailsWhenAbsentInOrder() '
  • Third Item
  • ', ]); } + + public function testMailableAssertSeeInTextWithApostrophePassesWhenPresent() + { + $mailable = new MailableAssertionsStub; + + $mailable->assertSeeInText("It's a wonderful day"); + } + + public function testMailableAssertSeeInTextWithApostropheFailsWhenAbsent() + { + $mailable = new MailableAssertionsStub; + + $this->expectException(AssertionFailedError::class); + + $mailable->assertSeeInText("It's not a wonderful day"); + } + + public function testMailableAssertDontSeeInTextWithApostrophePassesWhenAbsent() + { + $mailable = new MailableAssertionsStub; + + $mailable->assertDontSeeInText("It's not a wonderful day"); + } + + public function testMailableAssertDontSeeInTextWithApostropheFailsWhenPresent() + { + $mailable = new MailableAssertionsStub; + + $this->expectException(AssertionFailedError::class); + + $mailable->assertDontSeeInText("It's a wonderful day"); + } + + public function testMailableAssertSeeInHtmlWithApostropheFailsWhenAbsent() + { + $mailable = new MailableAssertionsStub; + + $this->expectException(AssertionFailedError::class); + + $mailable->assertSeeInHtml("
  • It's not a wonderful day
  • "); + } + + public function testMailableAssertDontSeeInHtmlWithApostrophePassesWhenAbsent() + { + $mailable = new MailableAssertionsStub; + + $mailable->assertDontSeeInHtml("
  • It's not a wonderful day
  • "); + } + + public function testMailableAssertDontSeeInHtmlWithApostropheFailsWhenPresent() + { + $mailable = new MailableAssertionsStub; + + $this->expectException(AssertionFailedError::class); + + $mailable->assertDontSeeInHtml("
  • It's a wonderful day
  • ", false); + } + + public function testMailableAssertSeeInOrderInHtmlWithApostrophePassesWhenPresentInOrder() + { + $mailable = new MailableAssertionsStub; + + $mailable->assertSeeInOrderInHtml([ + 'First Item', + 'Sixth Item', + 'It\'s a wonderful day', + ]); + + $mailable->assertSeeInOrderInHtml([ + '
  • First Item
  • ', + '
  • It\'s a wonderful day
  • ', + ], false); + } + + public function testMailableAssertSeeInOrderInHtmlWithApostropheFailsWhenAbsentInOrder() + { + $mailable = new MailableAssertionsStub; + + $this->expectException(AssertionFailedError::class); + + $mailable->assertSeeInOrderInHtml([ + 'It\'s a wonderful day', + 'First Item', + 'Sixth Item', + ]); + } } class MailableAssertionsStub extends Mailable @@ -149,6 +235,7 @@ protected function renderForAssertions() - Third Item - Fourth & Fifth Item - Sixth Item + - It's a wonderful day EOD; $html = <<<'EOD' @@ -167,6 +254,7 @@ protected function renderForAssertions()
  • Third Item
  • Fourth & Fifth Item
  • Sixth Item
  • +
  • It's a wonderful day
  • From 85b7ca8414f9486c37b0a5f87d81fb99aad17ddc Mon Sep 17 00:00:00 2001 From: Talgat Hairullov Date: Tue, 3 Sep 2024 17:32:15 +0400 Subject: [PATCH 004/100] [11.x] SQLite Error: "General error: 1 no such table" after adding a foreign key when using a table prefix. (#52578) * Added removal of the table prefix when defining a foreign key. * Code style fixed. --- .../Database/Schema/BlueprintState.php | 17 +- .../DatabaseSQLiteSchemaGrammarTest.php | 161 ++++++++++++++++++ 2 files changed, 177 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Schema/BlueprintState.php b/src/Illuminate/Database/Schema/BlueprintState.php index d617f58fac41..1fd5eeb5dc1c 100644 --- a/src/Illuminate/Database/Schema/BlueprintState.php +++ b/src/Illuminate/Database/Schema/BlueprintState.php @@ -105,7 +105,7 @@ public function __construct(Blueprint $blueprint, Connection $connection, Gramma $this->foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ 'columns' => $foreignKey['columns'], - 'on' => $foreignKey['foreign_table'], + 'on' => $this->withoutTablePrefix($foreignKey['foreign_table']), 'references' => $foreignKey['foreign_columns'], 'onUpdate' => $foreignKey['on_update'], 'onDelete' => $foreignKey['on_delete'], @@ -251,4 +251,19 @@ public function update(Fluent $command) break; } } + + /** + * Remove the table prefix from a table name, if it exists. + * + * @param string $table + * @return string + */ + protected function withoutTablePrefix(string $table) + { + $prefix = $this->connection->getTablePrefix(); + + return str_starts_with($table, $prefix) + ? substr($table, strlen($prefix)) + : $table; + } } diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index 6454a9d82c60..c7f61d32bfd0 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -7,8 +7,12 @@ use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\Processors\SQLiteProcessor; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Database\Schema\BlueprintState; +use Illuminate\Database\Schema\ColumnDefinition; use Illuminate\Database\Schema\ForeignIdColumnDefinition; +use Illuminate\Database\Schema\ForeignKeyDefinition; use Illuminate\Database\Schema\Grammars\SQLiteGrammar; +use Illuminate\Database\Schema\IndexDefinition; use Illuminate\Database\Schema\SQLiteBuilder; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -998,6 +1002,163 @@ public function testDroppingColumnsWorks() $this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql($this->getConnection(), $this->getGrammar())); } + public function testBlueprintInitialState() + { + $db = new Manager; + + $db->addConnection([ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => 'prefix_', + ]); + + $connection = $db->getConnection(); + $grammar = new SQLiteGrammar(); + $grammar->setConnection($connection); + $grammar->setTablePrefix($connection->getTablePrefix()); + + $schema = $connection->getSchemaBuilder(); + + $schema->create('users', function (Blueprint $table) { + $table->string('name'); + $table->string('email'); + }); + + $schema->create('posts', function (Blueprint $table) { + $table->increments('id'); + $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); + $table->integer('parent_id')->nullable()->index('parent_id'); + $table->string('status')->default('A'); + $table->boolean('is_active')->virtualAs('"status" = \'A\''); + $table->boolean('has_parent')->storedAs('"parent_id" IS NOT NULL'); + $table->string('title')->collation('RTRIM'); + }); + + $blueprint = new Blueprint('posts', null, $connection->getTablePrefix()); + $state = new BlueprintState($blueprint, $connection, $grammar); + + $this->assertCount(7, $state->getColumns()); + $this->assertCount(1, $state->getIndexes()); + $this->assertCount(1, $state->getForeignKeys()); + $this->assertSame(['id'], $state->getPrimaryKey()->get('columns')); + $this->assertSame( + [ + [ + 'name' => 'id', + 'type' => 'integer', + 'full_type_definition' => 'integer', + 'nullable' => false, + 'default' => null, + 'autoIncrement' => true, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + [ + 'name' => 'user_id', + 'type' => 'integer', + 'full_type_definition' => 'integer', + 'nullable' => false, + 'default' => null, + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + [ + 'name' => 'parent_id', + 'type' => 'integer', + 'full_type_definition' => 'integer', + 'nullable' => true, + 'default' => null, + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + [ + 'name' => 'status', + 'type' => 'varchar', + 'full_type_definition' => 'varchar', + 'nullable' => false, + 'default' => "'A'", + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + [ + 'name' => 'is_active', + 'type' => 'tinyint', + 'full_type_definition' => 'tinyint(1)', + 'nullable' => true, + 'default' => null, + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => '"status" = \'A\'', + 'storedAs' => null, + ], + [ + 'name' => 'has_parent', + 'type' => 'tinyint', + 'full_type_definition' => 'tinyint(1)', + 'nullable' => true, + 'default' => null, + 'autoIncrement' => false, + 'collation' => null, + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => '"parent_id" IS NOT NULL', + ], + [ + 'name' => 'title', + 'type' => 'varchar', + 'full_type_definition' => 'varchar', + 'nullable' => false, + 'default' => null, + 'autoIncrement' => false, + 'collation' => 'rtrim', + 'comment' => null, + 'virtualAs' => null, + 'storedAs' => null, + ], + ], + array_map( + fn (ColumnDefinition $definition) => array_replace($definition->toArray(), [ + 'default' => $definition->value('default') ? $definition->value('default')->getValue($grammar) : $definition->value('default'), + ]), + $state->getColumns() + ) + ); + $this->assertSame( + [ + [ + 'name' => 'index', + 'index' => 'parent_id', + 'columns' => ['parent_id'], + ], + ], + array_map(fn (IndexDefinition $definition) => $definition->toArray(), $state->getIndexes()) + ); + $this->assertSame( + [ + [ + 'columns' => ['user_id'], + 'on' => 'users', + 'references' => ['id'], + 'onUpdate' => 'no action', + 'onDelete' => 'cascade', + ], + ], + array_map(fn (ForeignKeyDefinition $definition) => $definition->toArray(), $state->getForeignKeys()) + ); + } + protected function getConnection() { $connection = m::mock(Connection::class); From 868c75beacc47d0f361b919bbc155c0b619bf3d5 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 3 Sep 2024 15:27:15 +0000 Subject: [PATCH 005/100] Update version to v11.22.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 211fce8ef438..7c82367b67ea 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.21.0'; + const VERSION = '11.22.0'; /** * The base path for the Laravel installation. From 7a3166c8d6a2cb6e5c0890ad6a5b7067ba7d2a20 Mon Sep 17 00:00:00 2001 From: driesvints Date: Tue, 3 Sep 2024 15:28:38 +0000 Subject: [PATCH 006/100] Update CHANGELOG --- CHANGELOG.md | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94f7c17fad56..d5b48cdd4347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,33 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.21.0...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.22.0...11.x) + +## [v11.22.0](https://github.com/laravel/framework/compare/v11.21.0...v11.22.0) - 2024-09-03 + +* [11.x] Fix FoundationServiceProvider docblock by [@seriquynh](https://github.com/seriquynh) in https://github.com/laravel/framework/pull/52542 +* [11.x] Fix ReflectionParameter [@param](https://github.com/param) type on Util::getContextualAttributeFromDependency() by [@samsonasik](https://github.com/samsonasik) in https://github.com/laravel/framework/pull/52541 +* [11.x] More specific parameter type in CastsInboundAttributes by [@lorenzolosa](https://github.com/lorenzolosa) in https://github.com/laravel/framework/pull/52536 +* [11.x] Unify prefetch API by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52550 +* [11.x] Add PDO subclass support for PHP 8.4 by [@ju5t](https://github.com/ju5t) in https://github.com/laravel/framework/pull/52538 +* [11.x] Handle circular references in model serialization by [@samlev](https://github.com/samlev) in https://github.com/laravel/framework/pull/52461 +* [11.x] Eloquent inverse relations by [@samlev](https://github.com/samlev) in https://github.com/laravel/framework/pull/51582 +* [11.x] Feature/whereany closures by [@liamduckett](https://github.com/liamduckett) in https://github.com/laravel/framework/pull/52555 +* [11.x] Update remaining workflows to run on latest possible ubuntu version by [@Jubeki](https://github.com/Jubeki) in https://github.com/laravel/framework/pull/52566 +* Correct comments to better represent the updated method functionality by [@dropweb](https://github.com/dropweb) in https://github.com/laravel/framework/pull/52564 +* [11.x] Support CSP nonce by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52558 +* [11.x] Allow enums to be passed to routes by [@NickSdot](https://github.com/NickSdot) in https://github.com/laravel/framework/pull/52561 +* [11.x] SORT_NATURAL on Collection no longer throws warning for nulls by [@Chaplinski](https://github.com/Chaplinski) in https://github.com/laravel/framework/pull/52557 +* [11.x] Allow prefetch to start on custom event by [@timacdonald](https://github.com/timacdonald) in https://github.com/laravel/framework/pull/52574 +* [11.x] Fix regression in database assertions with custom model connections by [@devfrey](https://github.com/devfrey) in https://github.com/laravel/framework/pull/52581 +* [11] Update DetectsLostConnections.php by [@webartisan10](https://github.com/webartisan10) in https://github.com/laravel/framework/pull/52614 +* Fix docblock for `Model::getEventDispatcher()` by [@inmula](https://github.com/inmula) in https://github.com/laravel/framework/pull/52602 +* [11.x] Restore Request::HEADER_X_FORWARDED_PREFIX in TrustProxies by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/52598 +* [11.x] Accepts BackedEnum for onQueue, onConnection, allOnQueue, and allOnConnection methods in the Queueable trait by [@sethsandaru](https://github.com/sethsandaru) in https://github.com/laravel/framework/pull/52604 +* [11.x] Use the same parameter type for 'throwUnless' as used for 'throwIf' by [@pataar](https://github.com/pataar) in https://github.com/laravel/framework/pull/52626 +* [11.x] Pass iterable keys to `withProgressBar` in InteractsWithIO by [@robinmoisson](https://github.com/robinmoisson) in https://github.com/laravel/framework/pull/52623 +* [11.x] Fix docblock for Filesystem::hash() by [@sunaoka](https://github.com/sunaoka) in https://github.com/laravel/framework/pull/52630 +* Fix Apostrophe Handling in SeeInOrder.php and Enhance Test Coverage by [@nomitoor](https://github.com/nomitoor) in https://github.com/laravel/framework/pull/52627 +* [11.x] SQLite Error: "General error: 1 no such table" after adding a foreign key when using a table prefix. by [@incrize](https://github.com/incrize) in https://github.com/laravel/framework/pull/52578 ## [v11.21.0](https://github.com/laravel/framework/compare/v11.20.0...v11.21.0) - 2024-08-20 From 769f00ba71de3b3cbbb271e9f34a019b584982c9 Mon Sep 17 00:00:00 2001 From: bastien-phi Date: Wed, 4 Sep 2024 16:05:32 +0200 Subject: [PATCH 007/100] Fix $fail closure type in docblocks for validation rules (#52644) --- src/Illuminate/Contracts/Validation/InvokableRule.php | 2 +- src/Illuminate/Contracts/Validation/ValidationRule.php | 2 +- src/Illuminate/Foundation/Console/stubs/rule.implicit.stub | 2 +- src/Illuminate/Foundation/Console/stubs/rule.stub | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Contracts/Validation/InvokableRule.php b/src/Illuminate/Contracts/Validation/InvokableRule.php index c7ac9c248def..bed9ed567fb4 100644 --- a/src/Illuminate/Contracts/Validation/InvokableRule.php +++ b/src/Illuminate/Contracts/Validation/InvokableRule.php @@ -14,7 +14,7 @@ interface InvokableRule * * @param string $attribute * @param mixed $value - * @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail + * @param \Closure(string, ?string = null): \Illuminate\Translation\PotentiallyTranslatedString $fail * @return void */ public function __invoke(string $attribute, mixed $value, Closure $fail); diff --git a/src/Illuminate/Contracts/Validation/ValidationRule.php b/src/Illuminate/Contracts/Validation/ValidationRule.php index 1c95b975c4bc..c687b26a2d98 100644 --- a/src/Illuminate/Contracts/Validation/ValidationRule.php +++ b/src/Illuminate/Contracts/Validation/ValidationRule.php @@ -11,7 +11,7 @@ interface ValidationRule * * @param string $attribute * @param mixed $value - * @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail + * @param \Closure(string, ?string = null): \Illuminate\Translation\PotentiallyTranslatedString $fail * @return void */ public function validate(string $attribute, mixed $value, Closure $fail): void; diff --git a/src/Illuminate/Foundation/Console/stubs/rule.implicit.stub b/src/Illuminate/Foundation/Console/stubs/rule.implicit.stub index 341f842a6271..e04915bf5852 100644 --- a/src/Illuminate/Foundation/Console/stubs/rule.implicit.stub +++ b/src/Illuminate/Foundation/Console/stubs/rule.implicit.stub @@ -17,7 +17,7 @@ class {{ class }} implements ValidationRule /** * Run the validation rule. * - * @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail + * @param \Closure(string, ?string = null): \Illuminate\Translation\PotentiallyTranslatedString $fail */ public function validate(string $attribute, mixed $value, Closure $fail): void { diff --git a/src/Illuminate/Foundation/Console/stubs/rule.stub b/src/Illuminate/Foundation/Console/stubs/rule.stub index e54d7efaedab..7b54420895b4 100644 --- a/src/Illuminate/Foundation/Console/stubs/rule.stub +++ b/src/Illuminate/Foundation/Console/stubs/rule.stub @@ -10,7 +10,7 @@ class {{ class }} implements ValidationRule /** * Run the validation rule. * - * @param \Closure(string): \Illuminate\Translation\PotentiallyTranslatedString $fail + * @param \Closure(string, ?string = null): \Illuminate\Translation\PotentiallyTranslatedString $fail */ public function validate(string $attribute, mixed $value, Closure $fail): void { From 45791414f54956a20788ef5560820fa26bed7859 Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Thu, 5 Sep 2024 22:06:10 +0200 Subject: [PATCH 008/100] [11.x] Add MSSQL 2017 and PGSQL 10 builds (#52631) * Add MSSQL 2017 and PGSQL 10 builds * Update databases.yml * Skip Tests (#52632) * [DB-Test] Assert different number of types in pgsql 10 (#52638) * Find out which types are not counted * dump types * Bypass count check for pgsql 10 --------- Co-authored-by: Julius Kiekbusch --- .github/workflows/databases.yml | 99 ++++++++++++++++++- .../Database/Postgres/FulltextTest.php | 4 + .../Postgres/PostgresSchemaBuilderTest.php | 4 + .../Database/SchemaBuilderTest.php | 11 ++- 4 files changed, 115 insertions(+), 3 deletions(-) diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index f9d387b11601..f4286e7b10af 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -144,7 +144,7 @@ jobs: env: DB_CONNECTION: mariadb - pgsql: + pgsql_14: runs-on: ubuntu-24.04 services: @@ -192,7 +192,55 @@ jobs: DB_USERNAME: forge DB_PASSWORD: password - mssql: + pgsql_10: + runs-on: ubuntu-24.04 + + services: + postgresql: + image: postgres:10 + env: + POSTGRES_DB: laravel + POSTGRES_USER: forge + POSTGRES_PASSWORD: password + ports: + - 5432:5432 + options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3 + + strategy: + fail-fast: true + + name: PostgreSQL 10 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, :php-psr + tools: composer:v2 + coverage: none + + - name: Set Framework version + run: composer config version "11.x-dev" + + - name: Install dependencies + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit tests/Integration/Database + env: + DB_CONNECTION: pgsql + DB_USERNAME: forge + DB_PASSWORD: password + + mssql_2019: runs-on: ubuntu-22.04 services: @@ -239,6 +287,53 @@ jobs: DB_USERNAME: SA DB_PASSWORD: Forge123 + mssql_2017: + runs-on: ubuntu-22.04 + + services: + sqlsrv: + image: mcr.microsoft.com/mssql/server:2017-latest + env: + ACCEPT_EULA: Y + SA_PASSWORD: Forge123 + ports: + - 1433:1433 + + strategy: + fail-fast: true + + name: SQL Server 2017 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.2 + extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr + tools: composer:v2 + coverage: none + + - name: Set Framework version + run: composer config version "11.x-dev" + + - name: Install dependencies + uses: nick-fields/retry@v3 + with: + timeout_minutes: 5 + max_attempts: 5 + command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + - name: Execute tests + run: vendor/bin/phpunit tests/Integration/Database + env: + DB_CONNECTION: sqlsrv + DB_DATABASE: master + DB_USERNAME: SA + DB_PASSWORD: Forge123 + sqlite: runs-on: ubuntu-24.04 diff --git a/tests/Integration/Database/Postgres/FulltextTest.php b/tests/Integration/Database/Postgres/FulltextTest.php index 7c8911f6c2b6..e174ab093eda 100644 --- a/tests/Integration/Database/Postgres/FulltextTest.php +++ b/tests/Integration/Database/Postgres/FulltextTest.php @@ -52,6 +52,10 @@ public function testWhereFulltext() public function testWhereFulltextWithWebsearch() { + if (version_compare($this->getConnection()->getServerVersion(), '11.0', '<')) { + $this->markTestSkipped('Test requires a PostgreSQL connection >= 11.0'); + } + $articles = DB::table('articles')->whereFulltext(['title', 'body'], '+PostgreSQL -YourSQL', ['mode' => 'websearch'])->get(); $this->assertCount(5, $articles); diff --git a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php index 48f2a0e74d71..58dc1b047109 100644 --- a/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php +++ b/tests/Integration/Database/Postgres/PostgresSchemaBuilderTest.php @@ -167,6 +167,10 @@ public function testGetViews() public function testDropPartitionedTables() { + if (version_compare($this->getConnection()->getServerVersion(), '11.0', '<')) { + $this->markTestSkipped('Test requires a PostgreSQL connection >= 11.0'); + } + DB::statement('create table groups (id bigserial, tenant_id bigint, name varchar, primary key (id, tenant_id)) partition by hash (tenant_id)'); DB::statement('create table groups_1 partition of groups for values with (modulus 2, remainder 0)'); DB::statement('create table groups_2 partition of groups for values with (modulus 2, remainder 1)'); diff --git a/tests/Integration/Database/SchemaBuilderTest.php b/tests/Integration/Database/SchemaBuilderTest.php index b0c1f67f3c9a..1d84f79fa0b4 100644 --- a/tests/Integration/Database/SchemaBuilderTest.php +++ b/tests/Integration/Database/SchemaBuilderTest.php @@ -355,7 +355,12 @@ public function testGetAndDropTypes() $types = Schema::getTypes(); - $this->assertCount(13, $types); + if (version_compare($this->getConnection()->getServerVersion(), '14.0', '<')) { + $this->assertCount(10, $types); + } else { + $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'])); @@ -669,6 +674,10 @@ public function testModifyingStoredColumnOnSqlite() public function testGettingGeneratedColumns() { + if ($this->driver === 'pgsql' && version_compare($this->getConnection()->getServerVersion(), '12.0', '<')) { + $this->markTestSkipped('Test requires a PostgreSQL connection >= 12.0'); + } + Schema::create('test', function (Blueprint $table) { $table->integer('price'); From 9ec747f4762dbdf858619aa823ab4160b0b553b5 Mon Sep 17 00:00:00 2001 From: Samuel Nitsche Date: Thu, 5 Sep 2024 22:07:49 +0200 Subject: [PATCH 009/100] Update `everyThirtyMinutes` cron expression (#52662) * Update cron syntax for better readability * Update test --- src/Illuminate/Console/Scheduling/ManagesFrequencies.php | 2 +- tests/Console/Scheduling/FrequencyTest.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Console/Scheduling/ManagesFrequencies.php b/src/Illuminate/Console/Scheduling/ManagesFrequencies.php index cc797b69e521..d974a91b769e 100644 --- a/src/Illuminate/Console/Scheduling/ManagesFrequencies.php +++ b/src/Illuminate/Console/Scheduling/ManagesFrequencies.php @@ -234,7 +234,7 @@ public function everyFifteenMinutes() */ public function everyThirtyMinutes() { - return $this->spliceIntoPosition(1, '0,30'); + return $this->spliceIntoPosition(1, '*/30'); } /** diff --git a/tests/Console/Scheduling/FrequencyTest.php b/tests/Console/Scheduling/FrequencyTest.php index fa9a8ea167f1..17cc6f398814 100644 --- a/tests/Console/Scheduling/FrequencyTest.php +++ b/tests/Console/Scheduling/FrequencyTest.php @@ -37,7 +37,7 @@ public function testEveryXMinutes() $this->assertSame('*/5 * * * *', $this->event->everyFiveMinutes()->getExpression()); $this->assertSame('*/10 * * * *', $this->event->everyTenMinutes()->getExpression()); $this->assertSame('*/15 * * * *', $this->event->everyFifteenMinutes()->getExpression()); - $this->assertSame('0,30 * * * *', $this->event->everyThirtyMinutes()->getExpression()); + $this->assertSame('*/30 * * * *', $this->event->everyThirtyMinutes()->getExpression()); } public function testDaily() From 839c2d0577603bf1a76f96aab2cce21107824fae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 Sep 2024 15:07:57 -0500 Subject: [PATCH 010/100] Bump micromatch (#52664) Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8. - [Release notes](https://github.com/micromatch/micromatch/releases) - [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8) --- updated-dependencies: - dependency-name: micromatch dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../resources/exceptions/renderer/package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json b/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json index 31d40205e4b8..f7d870c1517b 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json @@ -1336,11 +1336,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { From d8aabd9697e240df69c2cca26c05308db4b06020 Mon Sep 17 00:00:00 2001 From: lorenzolosa <11164571+lorenzolosa@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:59:09 +0200 Subject: [PATCH 011/100] apply excludeUnvalidatedArrayKeys to list validation (#52658) --- src/Illuminate/Validation/Validator.php | 2 +- tests/Validation/ValidationValidatorTest.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Validator.php b/src/Illuminate/Validation/Validator.php index 2dff098c11a2..8a0b2871f903 100755 --- a/src/Illuminate/Validation/Validator.php +++ b/src/Illuminate/Validation/Validator.php @@ -593,7 +593,7 @@ public function validated() $value = data_get($this->getData(), $key, $missingValue); if ($this->excludeUnvalidatedArrayKeys && - in_array('array', $rules) && + (in_array('array', $rules) || in_array('list', $rules)) && $value !== null && ! empty(preg_grep('/^'.preg_quote($key, '/').'\.+/', array_keys($this->getRules())))) { continue; diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index a1c49716febf..36935b84d417 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -8933,6 +8933,15 @@ public function testExcludingArrays() $validator->excludeUnvalidatedArrayKeys = true; $this->assertTrue($validator->passes()); $this->assertSame(['users' => [1, 2, 3]], $validator->validated()); + + $validator = new Validator( + $this->getIlluminateArrayTranslator(), + ['users' => [['name' => 'Mohamed', 'location' => 'cairo']]], + ['users' => 'list', 'users.*.name' => 'string'] + ); + $validator->excludeUnvalidatedArrayKeys = true; + $this->assertTrue($validator->passes()); + $this->assertSame(['users' => [['name' => 'Mohamed']]], $validator->validated()); } public function testExcludeUnless() From ffa4d15b3b69bba454776f79a09669d170185875 Mon Sep 17 00:00:00 2001 From: Cam Kemshal-Bell <112100521+CamKem@users.noreply.github.com> Date: Fri, 6 Sep 2024 07:11:59 +1000 Subject: [PATCH 012/100] [11.x] Adding minRatio & maxRatio rules on Dimension validation ruleset (#52482) * feat(dim-ratio): add min, max & range methods to Dimensions rule * feat(dim-ratio): add logic for what makes a min or max failing ratio * test(dim-ratio): add test for the new rules to the validator * test(dim-ratio): add more tests for coverage for the new methods on the Dimension rule test * test(dim-ratio): update condition where full ratio not provided in test (edge-case) * chore: styleci fixes * formatting * test: added validation message test * test: fixed naming * chore: styleci fixes --------- Co-authored-by: Taylor Otwell --- .../Concerns/ValidatesAttributes.php | 54 ++++++++++++++++-- .../Validation/Rules/Dimensions.php | 41 ++++++++++++++ .../ValidationDimensionsRuleTest.php | 35 ++++++++++++ .../ValidationImageFileRuleTest.php | 56 +++++++++++++++++++ tests/Validation/ValidationValidatorTest.php | 16 ++++++ 5 files changed, 196 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index 8da894b85620..b2727d8f9bb1 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -740,12 +740,12 @@ public function validateDimensions($attribute, $value, $parameters) $parameters = $this->parseNamedParameters($parameters); - if ($this->failsBasicDimensionChecks($parameters, $width, $height) || - $this->failsRatioCheck($parameters, $width, $height)) { - return false; - } - - return true; + return ! ( + $this->failsBasicDimensionChecks($parameters, $width, $height) || + $this->failsRatioCheck($parameters, $width, $height) || + $this->failsMinRatioCheck($parameters, $width, $height) || + $this->failsMaxRatioCheck($parameters, $width, $height) + ); } /** @@ -789,6 +789,48 @@ protected function failsRatioCheck($parameters, $width, $height) return abs($numerator / $denominator - $width / $height) > $precision; } + /** + * Determine if the given parameters fail a dimension minimum ratio check. + * + * @param array $parameters + * @param int $width + * @param int $height + * @return bool + */ + private function failsMinRatioCheck($parameters, $width, $height) + { + if (! isset($parameters['min_ratio'])) { + return false; + } + + [$minNumerator, $minDenominator] = array_replace( + [1, 1], array_filter(sscanf($parameters['min_ratio'], '%f/%d')) + ); + + return ($width / $height) > ($minNumerator / $minDenominator); + } + + /** + * Determine if the given parameters fail a dimension maximum ratio check. + * + * @param array $parameters + * @param int $width + * @param int $height + * @return bool + */ + private function failsMaxRatioCheck($parameters, $width, $height) + { + if (! isset($parameters['max_ratio'])) { + return false; + } + + [$maxNumerator, $maxDenominator] = array_replace( + [1, 1], array_filter(sscanf($parameters['max_ratio'], '%f/%d')) + ); + + return ($width / $height) < ($maxNumerator / $maxDenominator); + } + /** * Validate an attribute is unique among other values. * diff --git a/src/Illuminate/Validation/Rules/Dimensions.php b/src/Illuminate/Validation/Rules/Dimensions.php index 50d67fc379dd..bdbaf13a2ea1 100644 --- a/src/Illuminate/Validation/Rules/Dimensions.php +++ b/src/Illuminate/Validation/Rules/Dimensions.php @@ -118,6 +118,47 @@ public function ratio($value) return $this; } + /** + * Set the minimum aspect ratio. + * + * @param float $value + * @return $this + */ + public function minRatio($value) + { + $this->constraints['min_ratio'] = $value; + + return $this; + } + + /** + * Set the maximum aspect ratio. + * + * @param float $value + * @return $this + */ + public function maxRatio($value) + { + $this->constraints['max_ratio'] = $value; + + return $this; + } + + /** + * Set the aspect ratio range. + * + * @param float $min + * @param float $max + * @return $this + */ + public function ratioBetween($min, $max) + { + $this->constraints['min_ratio'] = $min; + $this->constraints['max_ratio'] = $max; + + return $this; + } + /** * Convert the rule to a validation string. * diff --git a/tests/Validation/ValidationDimensionsRuleTest.php b/tests/Validation/ValidationDimensionsRuleTest.php index 29d518a16081..8ff8c385eb52 100644 --- a/tests/Validation/ValidationDimensionsRuleTest.php +++ b/tests/Validation/ValidationDimensionsRuleTest.php @@ -2,8 +2,12 @@ namespace Illuminate\Tests\Validation; +use Illuminate\Http\UploadedFile; +use Illuminate\Translation\ArrayLoader; +use Illuminate\Translation\Translator; use Illuminate\Validation\Rule; use Illuminate\Validation\Rules\Dimensions; +use Illuminate\Validation\Validator; use PHPUnit\Framework\TestCase; class ValidationDimensionsRuleTest extends TestCase @@ -38,5 +42,36 @@ public function testItCorrectlyFormatsAStringVersionOfTheRule() $rule->width('200'); }); $this->assertSame('dimensions:height=100', (string) $rule); + + $rule = Rule::dimensions() + ->minRatio(1 / 2) + ->maxRatio(1 / 3); + $this->assertSame('dimensions:min_ratio=0.5,max_ratio=0.33333333333333', (string) $rule); + + $rule = Rule::dimensions() + ->ratioBetween(min: 1 / 2, max: 1 / 3); + $this->assertSame('dimensions:min_ratio=0.5,max_ratio=0.33333333333333', (string) $rule); + } + + public function testGeneratesTheCorrectValidationMessages() + { + $rule = Rule::dimensions() + ->width(100)->height(100) + ->ratioBetween(min: 1 / 2, max: 2 / 5); + + $trans = new Translator(new ArrayLoader, 'en'); + + $image = UploadedFile::fake(); + + $validator = new Validator( + $trans, + ['image' => $image], + ['image' => $rule] + ); + + $this->assertSame( + $trans->get('validation.dimensions', ['width' => 100, 'height' => 100, 'min_ratio' => 0.5, 'max_ratio' => 0.4]), + $validator->errors()->first('image') + ); } } diff --git a/tests/Validation/ValidationImageFileRuleTest.php b/tests/Validation/ValidationImageFileRuleTest.php index 7570b9c92155..63786a8fc61e 100644 --- a/tests/Validation/ValidationImageFileRuleTest.php +++ b/tests/Validation/ValidationImageFileRuleTest.php @@ -44,6 +44,62 @@ public function testDimensionsWithCustomImageSizeMethod() ); } + public function testDimentionWithTheRatioMethod() + { + $this->fails( + File::image()->dimensions(Rule::dimensions()->ratio(1)), + UploadedFile::fake()->image('foo.png', 105, 100), + ['validation.dimensions'], + ); + + $this->passes( + File::image()->dimensions(Rule::dimensions()->ratio(1)), + UploadedFile::fake()->image('foo.png', 100, 100), + ); + } + + public function testDimentionWithTheMinRatioMethod() + { + $this->fails( + File::image()->dimensions(Rule::dimensions()->minRatio(1 / 2)), + UploadedFile::fake()->image('foo.png', 100, 100), + ['validation.dimensions'], + ); + + $this->passes( + File::image()->dimensions(Rule::dimensions()->minRatio(1 / 2)), + UploadedFile::fake()->image('foo.png', 100, 200), + ); + } + + public function testDimentionWithTheMaxRatioMethod() + { + $this->fails( + File::image()->dimensions(Rule::dimensions()->maxRatio(1 / 2)), + UploadedFile::fake()->image('foo.png', 100, 300), + ['validation.dimensions'], + ); + + $this->passes( + File::image()->dimensions(Rule::dimensions()->maxRatio(1 / 2)), + UploadedFile::fake()->image('foo.png', 100, 100), + ); + } + + public function testDimentionWithTheRatioBetweenMethod() + { + $this->fails( + File::image()->dimensions(Rule::dimensions()->ratioBetween(1 / 2, 1 / 3)), + UploadedFile::fake()->image('foo.png', 100, 100), + ['validation.dimensions'], + ); + + $this->passes( + File::image()->dimensions(Rule::dimensions()->ratioBetween(1 / 2, 1 / 3)), + UploadedFile::fake()->image('foo.png', 100, 200), + ); + } + protected function fails($rule, $values, $messages) { $this->assertValidationRules($rule, $values, false, $messages); diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index 36935b84d417..ff4d156cb36c 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -4953,6 +4953,14 @@ public function testValidateImageDimensions() $v = new Validator($trans, ['x' => $uploadedFile], ['x' => 'dimensions:ratio=1']); $this->assertTrue($v->fails()); + // for min_ratio + $v = new Validator($trans, ['x' => $uploadedFile], ['x' => 'dimensions:min_ratio=1/2']); + $this->assertTrue($v->fails()); + + // for max_ratio + $v = new Validator($trans, ['x' => $uploadedFile], ['x' => 'dimensions:max_ratio=2/5']); + $this->assertTrue($v->passes()); + // Knowing that demo image2.png has width = 4 and height = 2 $uploadedFile = new UploadedFile(__DIR__.'/fixtures/image2.png', '', null, null, true); $trans = $this->getIlluminateArrayTranslator(); @@ -5010,6 +5018,14 @@ public function testValidateImageDimensions() $v = new Validator($trans, ['x' => $uploadedFile], ['x' => 'dimensions:ratio=1']); $this->assertFalse($v->passes()); + // evaluates to (64 / 65) > (1 / 1.0) which is true/fails + $v = new Validator($trans, ['x' => $uploadedFile], ['x' => 'dimensions:min_ratio=1']); + $this->assertFalse($v->fails()); + + // evaluates to (64 / 65) < (1 / 1.0) which is false/passes + $v = new Validator($trans, ['x' => $uploadedFile], ['x' => 'dimensions:max_ratio=1']); + $this->assertFalse($v->passes()); + // Knowing that demo image5.png has width = 1366 and height = 768 $uploadedFile = new UploadedFile(__DIR__.'/fixtures/image5.png', '', null, null, true); $trans = $this->getIlluminateArrayTranslator(); From 7bcf0e2fcca8528464c8f3d12d1e14dbc6d28bbc Mon Sep 17 00:00:00 2001 From: Diaa Fares Date: Fri, 6 Sep 2024 16:31:15 +0300 Subject: [PATCH 013/100] [11.x] Add BackedEnum support to Authorize middleware (#52679) * Add backed enum support * Add test --- src/Illuminate/Auth/Middleware/Authorize.php | 5 ++++- tests/Auth/AuthorizeMiddlewareTest.php | 21 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Auth/Middleware/Authorize.php b/src/Illuminate/Auth/Middleware/Authorize.php index 0b6ece4a5935..22fb4c5282a1 100644 --- a/src/Illuminate/Auth/Middleware/Authorize.php +++ b/src/Illuminate/Auth/Middleware/Authorize.php @@ -2,6 +2,7 @@ namespace Illuminate\Auth\Middleware; +use BackedEnum; use Closure; use Illuminate\Contracts\Auth\Access\Gate; use Illuminate\Database\Eloquent\Model; @@ -29,12 +30,14 @@ public function __construct(Gate $gate) /** * Specify the ability and models for the middleware. * - * @param string $ability + * @param \BackedEnum|string $ability * @param string ...$models * @return string */ public static function using($ability, ...$models) { + $ability = $ability instanceof BackedEnum ? $ability->value : $ability; + return static::class.':'.implode(',', [$ability, ...$models]); } diff --git a/tests/Auth/AuthorizeMiddlewareTest.php b/tests/Auth/AuthorizeMiddlewareTest.php index 0186d54536b7..7b03829a8e4f 100644 --- a/tests/Auth/AuthorizeMiddlewareTest.php +++ b/tests/Auth/AuthorizeMiddlewareTest.php @@ -122,6 +122,23 @@ public function testSimpleAbilityWithStringParameter() $this->assertSame('success', $response->content()); } + public function testSimpleAbilityWithBackedEnumParameter() + { + $this->gate()->define('view-dashboard', function ($user) { + return true; + }); + + $this->router->middleware(Authorize::using(AbilitiesEnum::VIEW_DASHBOARD))->get('dashboard', [ + 'uses' => function () { + return 'success'; + }, + ]); + + $response = $this->router->dispatch(Request::create('dashboard', 'GET')); + + $this->assertSame('success', $response->content()); + } + public function testSimpleAbilityWithNullParameter() { $this->gate()->define('view-dashboard', function ($user, $param = null) { @@ -334,3 +351,7 @@ protected function gate() return $this->container->make(GateContract::class); } } + +enum AbilitiesEnum: string { + case VIEW_DASHBOARD = 'view-dashboard'; +} From 51dacb0d3945319af35da41428740907bba08ce1 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 6 Sep 2024 13:31:38 +0000 Subject: [PATCH 014/100] Apply fixes from StyleCI --- tests/Auth/AuthorizeMiddlewareTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Auth/AuthorizeMiddlewareTest.php b/tests/Auth/AuthorizeMiddlewareTest.php index 7b03829a8e4f..a9c6059f43df 100644 --- a/tests/Auth/AuthorizeMiddlewareTest.php +++ b/tests/Auth/AuthorizeMiddlewareTest.php @@ -352,6 +352,7 @@ protected function gate() } } -enum AbilitiesEnum: string { +enum AbilitiesEnum: string +{ case VIEW_DASHBOARD = 'view-dashboard'; } From 32daea189f866aa9023e941d082094ba439ccd7a Mon Sep 17 00:00:00 2001 From: Diaa Fares Date: Fri, 6 Sep 2024 16:32:06 +0300 Subject: [PATCH 015/100] [11.x] Add BackedEnum support to Gate methods (#52677) * Add backed enum support to define * Support backed enum in gate methods * Add tests --- src/Illuminate/Auth/Access/Gate.php | 21 +++++--- tests/Auth/AuthAccessGateTest.php | 77 ++++++++++++++++++++++++++++- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Auth/Access/Gate.php b/src/Illuminate/Auth/Access/Gate.php index e9a574237385..746164843907 100644 --- a/src/Illuminate/Auth/Access/Gate.php +++ b/src/Illuminate/Auth/Access/Gate.php @@ -2,6 +2,7 @@ namespace Illuminate\Auth\Access; +use BackedEnum; use Closure; use Exception; use Illuminate\Auth\Access\Events\GateEvaluated; @@ -191,7 +192,7 @@ protected function authorizeOnDemand($condition, $message, $code, $allowWhenResp /** * Define a new ability. * - * @param string $ability + * @param \BackedEnum|string $ability * @param callable|array|string $callback * @return $this * @@ -199,6 +200,8 @@ protected function authorizeOnDemand($condition, $message, $code, $allowWhenResp */ public function define($ability, $callback) { + $ability = $ability instanceof BackedEnum ? $ability->value : $ability; + if (is_array($callback) && isset($callback[0]) && is_string($callback[0])) { $callback = $callback[0].'@'.$callback[1]; } @@ -320,7 +323,7 @@ public function after(callable $callback) /** * Determine if all of the given abilities should be granted for the current user. * - * @param iterable|string $ability + * @param iterable|\BackedEnum|string $ability * @param array|mixed $arguments * @return bool */ @@ -332,7 +335,7 @@ public function allows($ability, $arguments = []) /** * Determine if any of the given abilities should be denied for the current user. * - * @param iterable|string $ability + * @param iterable|\BackedEnum|string $ability * @param array|mixed $arguments * @return bool */ @@ -344,7 +347,7 @@ public function denies($ability, $arguments = []) /** * Determine if all of the given abilities should be granted for the current user. * - * @param iterable|string $abilities + * @param iterable|\BackedEnum|string $abilities * @param array|mixed $arguments * @return bool */ @@ -358,7 +361,7 @@ public function check($abilities, $arguments = []) /** * Determine if any one of the given abilities should be granted for the current user. * - * @param iterable|string $abilities + * @param iterable|\BackedEnum|string $abilities * @param array|mixed $arguments * @return bool */ @@ -370,7 +373,7 @@ public function any($abilities, $arguments = []) /** * Determine if all of the given abilities should be denied for the current user. * - * @param iterable|string $abilities + * @param iterable|\BackedEnum|string $abilities * @param array|mixed $arguments * @return bool */ @@ -382,7 +385,7 @@ public function none($abilities, $arguments = []) /** * Determine if the given ability should be granted for the current user. * - * @param string $ability + * @param \BackedEnum|string $ability * @param array|mixed $arguments * @return \Illuminate\Auth\Access\Response * @@ -396,12 +399,14 @@ public function authorize($ability, $arguments = []) /** * Inspect the user for the given ability. * - * @param string $ability + * @param \BackedEnum|string $ability * @param array|mixed $arguments * @return \Illuminate\Auth\Access\Response */ public function inspect($ability, $arguments = []) { + $ability = $ability instanceof BackedEnum ? $ability->value : $ability; + try { $result = $this->raw($ability, $arguments); diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index 717c09aeb751..8fb4b75f102c 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -326,6 +326,40 @@ public function testAfterCallbacksDoNotOverrideEachOther() $this->assertTrue($gate->denies('deny')); } + public function testCanDefineGatesUsingBackedEnum() + { + $gate = $this->getBasicGate(); + + $gate->define(AbilitiesEnum::VIEW_DASHBOARD, function ($user) { + return true; + }); + + $this->assertTrue($gate->allows('view-dashboard')); + } + + public function testBackedEnumInAllows() + { + $gate = $this->getBasicGate(); + + $gate->define(AbilitiesEnum::VIEW_DASHBOARD, function ($user) { + return true; + }); + + $this->assertTrue($gate->allows(AbilitiesEnum::VIEW_DASHBOARD)); + } + + + public function testBackedEnumInDenies() + { + $gate = $this->getBasicGate(); + + $gate->define(AbilitiesEnum::VIEW_DASHBOARD, function ($user) { + return false; + }); + + $this->assertTrue($gate->denies(AbilitiesEnum::VIEW_DASHBOARD)); + } + public function testArrayAbilitiesInAllows() { $gate = $this->getBasicGate(); @@ -336,12 +370,15 @@ public function testArrayAbilitiesInAllows() $gate->define('allow_2', function ($user) { return true; }); + $gate->define(AbilitiesEnum::VIEW_DASHBOARD, function ($user) { + return true; + }); $gate->define('deny', function ($user) { return false; }); $this->assertTrue($gate->allows(['allow_1'])); - $this->assertTrue($gate->allows(['allow_1', 'allow_2'])); + $this->assertTrue($gate->allows(['allow_1', 'allow_2', AbilitiesEnum::VIEW_DASHBOARD])); $this->assertFalse($gate->allows(['allow_1', 'allow_2', 'deny'])); $this->assertFalse($gate->allows(['deny', 'allow_1', 'allow_2'])); } @@ -356,12 +393,15 @@ public function testArrayAbilitiesInDenies() $gate->define('deny_2', function ($user) { return false; }); + $gate->define(AbilitiesEnum::VIEW_DASHBOARD, function ($user) { + return false; + }); $gate->define('allow', function ($user) { return true; }); $this->assertTrue($gate->denies(['deny_1'])); - $this->assertTrue($gate->denies(['deny_1', 'deny_2'])); + $this->assertTrue($gate->denies(['deny_1', 'deny_2', AbilitiesEnum::VIEW_DASHBOARD])); $this->assertTrue($gate->denies(['deny_1', 'allow'])); $this->assertTrue($gate->denies(['allow', 'deny_1'])); $this->assertFalse($gate->denies(['allow'])); @@ -1075,6 +1115,34 @@ public function testEveryAbilityCheckFailsIfNonePass() $this->assertFalse($gate->check(['edit', 'update'], new AccessGateTestDummy)); } + public function testAnyAbilitiesCheckUsingBackedEnum() + { + $gate = $this->getBasicGate(); + + $gate->policy(AccessGateTestDummy::class, AccessGateTestPolicyWithAllPermissions::class); + + $this->assertTrue($gate->any(['edit', AbilitiesEnum::UPDATE], new AccessGateTestDummy)); + } + + public function testNoneAbilitiesCheckUsingBackedEnum() + { + $gate = $this->getBasicGate(); + + $gate->policy(AccessGateTestDummy::class, AccessGateTestPolicyWithNoPermissions::class); + + $this->assertTrue($gate->none(['edit', AbilitiesEnum::UPDATE], new AccessGateTestDummy)); + } + + public function testAbilitiesCheckUsingBackedEnum() + { + $gate = $this->getBasicGate(); + + $gate->policy(AccessGateTestDummy::class, AccessGateTestPolicyWithAllPermissions::class); + + $this->assertTrue($gate->check(['edit', AbilitiesEnum::UPDATE], new AccessGateTestDummy)); + } + + /** * @param array $abilitiesToSet * @param array|string $abilitiesToCheck @@ -1475,3 +1543,8 @@ public function create() throw new AuthorizationException('Not allowed.', 'some_code'); } } + +enum AbilitiesEnum: string { + case VIEW_DASHBOARD = 'view-dashboard'; + case UPDATE = 'update'; +} From 01c9d30533cd4b0eb20527e7140676b41ae192cc Mon Sep 17 00:00:00 2001 From: Dries Vints Date: Fri, 6 Sep 2024 15:33:47 +0200 Subject: [PATCH 016/100] Suggest serialisable-closure (#52673) --- src/Illuminate/Support/composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 5be0cdb4c2d7..8995ed9655fc 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -47,6 +47,7 @@ }, "suggest": { "illuminate/filesystem": "Required to use the composer class (^11.0).", + "laravel/serializable-closure": "Required to use the once function (^1.3).", "league/commonmark": "Required to use Str::markdown() and Stringable::markdown() (^2.0.2).", "ramsey/uuid": "Required to use Str::uuid() (^4.7).", "symfony/process": "Required to use the composer class (^7.0).", From f6f3406ad1e690efe85db2d0f1350ce3410be1c4 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Fri, 6 Sep 2024 13:33:59 +0000 Subject: [PATCH 017/100] Apply fixes from StyleCI --- tests/Auth/AuthAccessGateTest.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index 8fb4b75f102c..bb50a9462fff 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -348,7 +348,6 @@ public function testBackedEnumInAllows() $this->assertTrue($gate->allows(AbilitiesEnum::VIEW_DASHBOARD)); } - public function testBackedEnumInDenies() { $gate = $this->getBasicGate(); @@ -1142,7 +1141,6 @@ public function testAbilitiesCheckUsingBackedEnum() $this->assertTrue($gate->check(['edit', AbilitiesEnum::UPDATE], new AccessGateTestDummy)); } - /** * @param array $abilitiesToSet * @param array|string $abilitiesToCheck @@ -1544,7 +1542,8 @@ public function create() } } -enum AbilitiesEnum: string { +enum AbilitiesEnum: string +{ case VIEW_DASHBOARD = 'view-dashboard'; case UPDATE = 'update'; } From d7249911406c0be2e83c19e0a970039c954095c7 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Fri, 6 Sep 2024 13:34:29 +0000 Subject: [PATCH 018/100] Update facade docblocks --- src/Illuminate/Support/Facades/Gate.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Support/Facades/Gate.php b/src/Illuminate/Support/Facades/Gate.php index 4423a1d6a0fb..6f12f13e0b63 100644 --- a/src/Illuminate/Support/Facades/Gate.php +++ b/src/Illuminate/Support/Facades/Gate.php @@ -8,18 +8,18 @@ * @method static bool has(string|array $ability) * @method static \Illuminate\Auth\Access\Response allowIf(\Illuminate\Auth\Access\Response|\Closure|bool $condition, string|null $message = null, string|null $code = null) * @method static \Illuminate\Auth\Access\Response denyIf(\Illuminate\Auth\Access\Response|\Closure|bool $condition, string|null $message = null, string|null $code = null) - * @method static \Illuminate\Auth\Access\Gate define(string $ability, callable|array|string $callback) + * @method static \Illuminate\Auth\Access\Gate define(\BackedEnum|string $ability, callable|array|string $callback) * @method static \Illuminate\Auth\Access\Gate resource(string $name, string $class, array|null $abilities = null) * @method static \Illuminate\Auth\Access\Gate policy(string $class, string $policy) * @method static \Illuminate\Auth\Access\Gate before(callable $callback) * @method static \Illuminate\Auth\Access\Gate after(callable $callback) - * @method static bool allows(iterable|string $ability, array|mixed $arguments = []) - * @method static bool denies(iterable|string $ability, array|mixed $arguments = []) - * @method static bool check(iterable|string $abilities, array|mixed $arguments = []) - * @method static bool any(iterable|string $abilities, array|mixed $arguments = []) - * @method static bool none(iterable|string $abilities, array|mixed $arguments = []) - * @method static \Illuminate\Auth\Access\Response authorize(string $ability, array|mixed $arguments = []) - * @method static \Illuminate\Auth\Access\Response inspect(string $ability, array|mixed $arguments = []) + * @method static bool allows(iterable|\BackedEnum|string $ability, array|mixed $arguments = []) + * @method static bool denies(iterable|\BackedEnum|string $ability, array|mixed $arguments = []) + * @method static bool check(iterable|\BackedEnum|string $abilities, array|mixed $arguments = []) + * @method static bool any(iterable|\BackedEnum|string $abilities, array|mixed $arguments = []) + * @method static bool none(iterable|\BackedEnum|string $abilities, array|mixed $arguments = []) + * @method static \Illuminate\Auth\Access\Response authorize(\BackedEnum|string $ability, array|mixed $arguments = []) + * @method static \Illuminate\Auth\Access\Response inspect(\BackedEnum|string $ability, array|mixed $arguments = []) * @method static mixed raw(string $ability, array|mixed $arguments = []) * @method static mixed getPolicyFor(object|string $class) * @method static \Illuminate\Auth\Access\Gate guessPolicyNamesUsing(callable $callback) From 2714be744062fba49b2ea39a19e835166216ba86 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Fri, 6 Sep 2024 17:08:22 +0330 Subject: [PATCH 019/100] [11.x] Fix alter table expressions on SQLite (#52678) * Revert "[11.x] SQLite Error: "General error: 1 no such table" after adding a foreign key when using a table prefix. (#52578)" This reverts commit 85b7ca8414f9486c37b0a5f87d81fb99aad17ddc. * fixes #52578 * fixes #52655 * formatting --- .../Database/Schema/BlueprintState.php | 20 +-- .../DatabaseSQLiteSchemaGrammarTest.php | 161 ------------------ .../Database/DatabaseSchemaBlueprintTest.php | 2 +- .../Database/DatabaseSchemaBuilderTest.php | 59 +++++++ 4 files changed, 63 insertions(+), 179 deletions(-) diff --git a/src/Illuminate/Database/Schema/BlueprintState.php b/src/Illuminate/Database/Schema/BlueprintState.php index 1fd5eeb5dc1c..3d074925ccf9 100644 --- a/src/Illuminate/Database/Schema/BlueprintState.php +++ b/src/Illuminate/Database/Schema/BlueprintState.php @@ -6,6 +6,7 @@ use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Grammars\Grammar; use Illuminate\Support\Fluent; +use Illuminate\Support\Str; class BlueprintState { @@ -80,7 +81,7 @@ public function __construct(Blueprint $blueprint, Connection $connection, Gramma 'type' => $column['type_name'], 'full_type_definition' => $column['type'], 'nullable' => $column['nullable'], - 'default' => is_null($column['default']) ? null : new Expression($column['default']), + 'default' => is_null($column['default']) ? null : new Expression(Str::wrap($column['default'], '(', ')')), 'autoIncrement' => $column['auto_increment'], 'collation' => $column['collation'], 'comment' => $column['comment'], @@ -105,7 +106,7 @@ public function __construct(Blueprint $blueprint, Connection $connection, Gramma $this->foreignKeys = collect($schema->getForeignKeys($table))->map(fn ($foreignKey) => new ForeignKeyDefinition([ 'columns' => $foreignKey['columns'], - 'on' => $this->withoutTablePrefix($foreignKey['foreign_table']), + 'on' => new Expression($foreignKey['foreign_table']), 'references' => $foreignKey['foreign_columns'], 'onUpdate' => $foreignKey['on_update'], 'onDelete' => $foreignKey['on_delete'], @@ -251,19 +252,4 @@ public function update(Fluent $command) break; } } - - /** - * Remove the table prefix from a table name, if it exists. - * - * @param string $table - * @return string - */ - protected function withoutTablePrefix(string $table) - { - $prefix = $this->connection->getTablePrefix(); - - return str_starts_with($table, $prefix) - ? substr($table, strlen($prefix)) - : $table; - } } diff --git a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php index c7f61d32bfd0..6454a9d82c60 100755 --- a/tests/Database/DatabaseSQLiteSchemaGrammarTest.php +++ b/tests/Database/DatabaseSQLiteSchemaGrammarTest.php @@ -7,12 +7,8 @@ use Illuminate\Database\Query\Expression; use Illuminate\Database\Query\Processors\SQLiteProcessor; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Database\Schema\BlueprintState; -use Illuminate\Database\Schema\ColumnDefinition; use Illuminate\Database\Schema\ForeignIdColumnDefinition; -use Illuminate\Database\Schema\ForeignKeyDefinition; use Illuminate\Database\Schema\Grammars\SQLiteGrammar; -use Illuminate\Database\Schema\IndexDefinition; use Illuminate\Database\Schema\SQLiteBuilder; use Mockery as m; use PHPUnit\Framework\TestCase; @@ -1002,163 +998,6 @@ public function testDroppingColumnsWorks() $this->assertEquals(['alter table "users" drop column "name"'], $blueprint->toSql($this->getConnection(), $this->getGrammar())); } - public function testBlueprintInitialState() - { - $db = new Manager; - - $db->addConnection([ - 'driver' => 'sqlite', - 'database' => ':memory:', - 'prefix' => 'prefix_', - ]); - - $connection = $db->getConnection(); - $grammar = new SQLiteGrammar(); - $grammar->setConnection($connection); - $grammar->setTablePrefix($connection->getTablePrefix()); - - $schema = $connection->getSchemaBuilder(); - - $schema->create('users', function (Blueprint $table) { - $table->string('name'); - $table->string('email'); - }); - - $schema->create('posts', function (Blueprint $table) { - $table->increments('id'); - $table->foreignId('user_id')->constrained('users')->cascadeOnDelete(); - $table->integer('parent_id')->nullable()->index('parent_id'); - $table->string('status')->default('A'); - $table->boolean('is_active')->virtualAs('"status" = \'A\''); - $table->boolean('has_parent')->storedAs('"parent_id" IS NOT NULL'); - $table->string('title')->collation('RTRIM'); - }); - - $blueprint = new Blueprint('posts', null, $connection->getTablePrefix()); - $state = new BlueprintState($blueprint, $connection, $grammar); - - $this->assertCount(7, $state->getColumns()); - $this->assertCount(1, $state->getIndexes()); - $this->assertCount(1, $state->getForeignKeys()); - $this->assertSame(['id'], $state->getPrimaryKey()->get('columns')); - $this->assertSame( - [ - [ - 'name' => 'id', - 'type' => 'integer', - 'full_type_definition' => 'integer', - 'nullable' => false, - 'default' => null, - 'autoIncrement' => true, - 'collation' => null, - 'comment' => null, - 'virtualAs' => null, - 'storedAs' => null, - ], - [ - 'name' => 'user_id', - 'type' => 'integer', - 'full_type_definition' => 'integer', - 'nullable' => false, - 'default' => null, - 'autoIncrement' => false, - 'collation' => null, - 'comment' => null, - 'virtualAs' => null, - 'storedAs' => null, - ], - [ - 'name' => 'parent_id', - 'type' => 'integer', - 'full_type_definition' => 'integer', - 'nullable' => true, - 'default' => null, - 'autoIncrement' => false, - 'collation' => null, - 'comment' => null, - 'virtualAs' => null, - 'storedAs' => null, - ], - [ - 'name' => 'status', - 'type' => 'varchar', - 'full_type_definition' => 'varchar', - 'nullable' => false, - 'default' => "'A'", - 'autoIncrement' => false, - 'collation' => null, - 'comment' => null, - 'virtualAs' => null, - 'storedAs' => null, - ], - [ - 'name' => 'is_active', - 'type' => 'tinyint', - 'full_type_definition' => 'tinyint(1)', - 'nullable' => true, - 'default' => null, - 'autoIncrement' => false, - 'collation' => null, - 'comment' => null, - 'virtualAs' => '"status" = \'A\'', - 'storedAs' => null, - ], - [ - 'name' => 'has_parent', - 'type' => 'tinyint', - 'full_type_definition' => 'tinyint(1)', - 'nullable' => true, - 'default' => null, - 'autoIncrement' => false, - 'collation' => null, - 'comment' => null, - 'virtualAs' => null, - 'storedAs' => '"parent_id" IS NOT NULL', - ], - [ - 'name' => 'title', - 'type' => 'varchar', - 'full_type_definition' => 'varchar', - 'nullable' => false, - 'default' => null, - 'autoIncrement' => false, - 'collation' => 'rtrim', - 'comment' => null, - 'virtualAs' => null, - 'storedAs' => null, - ], - ], - array_map( - fn (ColumnDefinition $definition) => array_replace($definition->toArray(), [ - 'default' => $definition->value('default') ? $definition->value('default')->getValue($grammar) : $definition->value('default'), - ]), - $state->getColumns() - ) - ); - $this->assertSame( - [ - [ - 'name' => 'index', - 'index' => 'parent_id', - 'columns' => ['parent_id'], - ], - ], - array_map(fn (IndexDefinition $definition) => $definition->toArray(), $state->getIndexes()) - ); - $this->assertSame( - [ - [ - 'columns' => ['user_id'], - 'on' => 'users', - 'references' => ['id'], - 'onUpdate' => 'no action', - 'onDelete' => 'cascade', - ], - ], - array_map(fn (ForeignKeyDefinition $definition) => $definition->toArray(), $state->getForeignKeys()) - ); - } - protected function getConnection() { $connection = m::mock(Connection::class); diff --git a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php index dbe7a470fade..1b3a939d25e8 100644 --- a/tests/Integration/Database/DatabaseSchemaBlueprintTest.php +++ b/tests/Integration/Database/DatabaseSchemaBlueprintTest.php @@ -321,7 +321,7 @@ public function testChangingColumnsWithDefaultWorks() $queries = $blueprint->toSql(DB::connection(), new SQLiteGrammar); $expected = [ - 'create table "__temp__products" ("changed_col" text not null, "timestamp_col" datetime not null default CURRENT_TIMESTAMP, "integer_col" integer not null default \'123\', "string_col" varchar not null default \'value\')', + 'create table "__temp__products" ("changed_col" text not null, "timestamp_col" datetime not null default (CURRENT_TIMESTAMP), "integer_col" integer not null default (\'123\'), "string_col" varchar not null default (\'value\'))', 'insert into "__temp__products" ("changed_col", "timestamp_col", "integer_col", "string_col") select "changed_col", "timestamp_col", "integer_col", "string_col" from "products"', 'drop table "products"', 'alter table "__temp__products" rename to "products"', diff --git a/tests/Integration/Database/DatabaseSchemaBuilderTest.php b/tests/Integration/Database/DatabaseSchemaBuilderTest.php index 8a377d3616d7..95a92d54d10d 100644 --- a/tests/Integration/Database/DatabaseSchemaBuilderTest.php +++ b/tests/Integration/Database/DatabaseSchemaBuilderTest.php @@ -2,6 +2,7 @@ namespace Illuminate\Tests\Integration\Database; +use Illuminate\Database\Query\Expression; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; @@ -77,4 +78,62 @@ public function testHasColumnAndIndexWithPrefixIndexEnabled() $this->assertContains('example_table1_name_index', $indexes); } + + public function testAlterTableAddForeignKeyWithPrefix() + { + $schema = Schema::connection('sqlite-with-prefix'); + + $schema->create('table1', function (Blueprint $table) { + $table->id(); + }); + + $schema->create('table2', function (Blueprint $table) { + $table->id(); + $table->foreignId('author_id')->constrained('table1'); + }); + + $schema->table('table2', function (Blueprint $table) { + $table->foreignId('moderator_id')->constrained('table1'); + }); + + $foreignKeys = collect($schema->getForeignKeys('table2')); + + $this->assertTrue($foreignKeys->contains( + fn ($fk) => $fk['foreign_table'] === 'example_table1' && + $fk['foreign_columns'] === ['id'] && + $fk['columns'] === ['author_id']) + ); + + $this->assertTrue($foreignKeys->contains( + fn ($fk) => $fk['foreign_table'] === 'example_table1' && + $fk['foreign_columns'] === ['id'] && + $fk['columns'] === ['moderator_id']) + ); + } + + public function testAlterTableAddForeignKeyWithExpressionDefault() + { + Schema::create('items', function (Blueprint $table) { + $table->id(); + $table->json('flags')->default(new Expression('(JSON_ARRAY())')); + }); + + Schema::table('items', function (Blueprint $table) { + $table->foreignId('item_id')->nullable()->constrained('items'); + }); + + $this->assertTrue(collect(Schema::getForeignKeys('items'))->contains( + fn ($fk) => $fk['foreign_table'] === 'items' && + $fk['foreign_columns'] === ['id'] && + $fk['columns'] === ['item_id'] + )); + + $columns = Schema::getColumns('items'); + + $this->assertTrue(collect($columns)->contains( + fn ($column) => $column['name'] === 'flags' && $column['default'] === 'JSON_ARRAY()' + )); + + $this->assertTrue(collect($columns)->contains(fn ($column) => $column['name'] === 'item_id' && $column['nullable'])); + } } From bb670ae486579b6121dc21f3dee35a1b50825746 Mon Sep 17 00:00:00 2001 From: Takayasu Oyama Date: Fri, 6 Sep 2024 22:41:41 +0900 Subject: [PATCH 020/100] [11.x] Add Exceptions\Handler::mapLogLevel(...) so the logic can be easily overridden (#52666) * [11.x] Add Exceptions\Handler::mapLogLevel so the logic can be easily overridden * formatting * formatting --------- Co-authored-by: Taylor Otwell --- .../Foundation/Exceptions/Handler.php | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index 6da808a4f352..4effa0c9990d 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -368,9 +368,7 @@ protected function reportThrowable(Throwable $e): void throw $e; } - $level = Arr::first( - $this->levels, fn ($level, $type) => $e instanceof $type, LogLevel::ERROR - ); + $level = $this->mapLogLevel($e); $context = $this->buildExceptionContext($e); @@ -1047,6 +1045,19 @@ protected function isHttpException(Throwable $e) return $e instanceof HttpExceptionInterface; } + /** + * Map the exception to a log level. + * + * @param \Throwable $e + * @return \Psr\Log\LogLevel::* + */ + protected function mapLogLevel(Throwable $e) + { + return Arr::first( + $this->levels, fn ($level, $type) => $e instanceof $type, LogLevel::ERROR + ); + } + /** * Create a new logger instance. * From caec2344827dc39f06b6aaa9fdcca45f957608b8 Mon Sep 17 00:00:00 2001 From: Samuel Levy Date: Fri, 6 Sep 2024 23:52:20 +1000 Subject: [PATCH 021/100] [11.x] Bugfix for calling pluck() on chaperoned relations. (#52680) * Added failing test for hydrating inverse relations This test should fail if non-models are passed to `applyInverseRelationToCollection()`, e.g. when `pluck()` is called on a query. * Only attempt inverse hydration on models. --- .../Concerns/SupportsInverseRelations.php | 2 +- .../DatabaseEloquentInverseRelationTest.php | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/Concerns/SupportsInverseRelations.php b/src/Illuminate/Database/Eloquent/Relations/Concerns/SupportsInverseRelations.php index fa0161520728..6fd76ec0cae4 100644 --- a/src/Illuminate/Database/Eloquent/Relations/Concerns/SupportsInverseRelations.php +++ b/src/Illuminate/Database/Eloquent/Relations/Concerns/SupportsInverseRelations.php @@ -97,7 +97,7 @@ protected function applyInverseRelationToCollection($models, ?Model $parent = nu $parent ??= $this->getParent(); foreach ($models as $model) { - $this->applyInverseRelationToModel($model, $parent); + $model instanceof Model && $this->applyInverseRelationToModel($model, $parent); } return $models; diff --git a/tests/Database/DatabaseEloquentInverseRelationTest.php b/tests/Database/DatabaseEloquentInverseRelationTest.php index 100921ea18da..ae76ca07f81e 100755 --- a/tests/Database/DatabaseEloquentInverseRelationTest.php +++ b/tests/Database/DatabaseEloquentInverseRelationTest.php @@ -272,6 +272,27 @@ public function testSetsGuessedInverseRelationBasedOnForeignKey() $this->assertSame('test', $relation->getInverseRelationship()); } + public function testOnlyHydratesInverseRelationOnModels() + { + $relation = m::mock(HasInverseRelationStub::class)->shouldAllowMockingProtectedMethods()->makePartial(); + $relation->shouldReceive('getParent')->andReturn(new HasInverseRelationParentStub); + $relation->shouldReceive('applyInverseRelationToModel')->times(6); + $relation->exposeApplyInverseRelationToCollection([ + new HasInverseRelationRelatedStub(), + 12345, + new HasInverseRelationRelatedStub(), + new HasInverseRelationRelatedStub(), + Model::class, + new HasInverseRelationRelatedStub(), + true, + [], + new HasInverseRelationRelatedStub(), + 'foo', + new class() {}, + new HasInverseRelationRelatedStub(), + ]); + } + public static function guessedParentRelationsDataProvider() { yield ['hasInverseRelationParentStub']; @@ -361,4 +382,9 @@ public function exposeGuessInverseRelation(): string|null { return $this->guessInverseRelation(); } + + public function exposeApplyInverseRelationToCollection($models, ?Model $parent = null) + { + return $this->applyInverseRelationToCollection($models, $parent); + } } From 455598c577fab3898e1d84ddbaa142780c8b192d Mon Sep 17 00:00:00 2001 From: Diaa Fares Date: Fri, 6 Sep 2024 23:27:22 +0300 Subject: [PATCH 022/100] [11.x] Fix build failures due to enum collide After adding BackedEnum support to Gate (#52683) * Fix build failures due to enum collide * Fix lint --- tests/Auth/AuthAccessGateTest.php | 8 ++------ tests/Auth/AuthorizeMiddlewareTest.php | 7 ++----- tests/Auth/Enums.php | 9 +++++++++ 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 tests/Auth/Enums.php diff --git a/tests/Auth/AuthAccessGateTest.php b/tests/Auth/AuthAccessGateTest.php index bb50a9462fff..128ac8daca8e 100644 --- a/tests/Auth/AuthAccessGateTest.php +++ b/tests/Auth/AuthAccessGateTest.php @@ -12,6 +12,8 @@ use PHPUnit\Framework\TestCase; use stdClass; +include_once 'Enums.php'; + class AuthAccessGateTest extends TestCase { public function testBasicClosuresCanBeDefined() @@ -1541,9 +1543,3 @@ public function create() throw new AuthorizationException('Not allowed.', 'some_code'); } } - -enum AbilitiesEnum: string -{ - case VIEW_DASHBOARD = 'view-dashboard'; - case UPDATE = 'update'; -} diff --git a/tests/Auth/AuthorizeMiddlewareTest.php b/tests/Auth/AuthorizeMiddlewareTest.php index a9c6059f43df..d9ca83e78b5d 100644 --- a/tests/Auth/AuthorizeMiddlewareTest.php +++ b/tests/Auth/AuthorizeMiddlewareTest.php @@ -19,6 +19,8 @@ use PHPUnit\Framework\TestCase; use stdClass; +include_once 'Enums.php'; + class AuthorizeMiddlewareTest extends TestCase { protected $container; @@ -351,8 +353,3 @@ protected function gate() return $this->container->make(GateContract::class); } } - -enum AbilitiesEnum: string -{ - case VIEW_DASHBOARD = 'view-dashboard'; -} diff --git a/tests/Auth/Enums.php b/tests/Auth/Enums.php new file mode 100644 index 000000000000..0e93ee25575e --- /dev/null +++ b/tests/Auth/Enums.php @@ -0,0 +1,9 @@ + Date: Fri, 6 Sep 2024 17:28:27 -0300 Subject: [PATCH 023/100] Fixing Str::trim to remove the default trim/ltrim/rtim characters " \n\r\t\v\0" (#52684) * Fixing Str::trim to remove the default characters which are " \n\r\t\v\0" Fixes #52681 (https://github.com/laravel/framework/issues/52681) * Fixing Str::trim to remove the default characters which are " \n\r\t\v\0" Fixes #52681 (https://github.com/laravel/framework/issues/52681) --- src/Illuminate/Support/Str.php | 12 +++++++++--- tests/Support/SupportStrTest.php | 30 ++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Str.php b/src/Illuminate/Support/Str.php index 52867d89e473..2cc8d6a72d02 100644 --- a/src/Illuminate/Support/Str.php +++ b/src/Illuminate/Support/Str.php @@ -1497,7 +1497,9 @@ public static function snake($value, $delimiter = '_') public static function trim($value, $charlist = null) { if ($charlist === null) { - return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}]+|[\s\x{FEFF}\x{200B}\x{200E}]+$~u', '', $value) ?? trim($value); + $trimDefaultCharacters = " \n\r\t\v\0"; + + return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}'.$trimDefaultCharacters.']+|[\s\x{FEFF}\x{200B}\x{200E}'.$trimDefaultCharacters.']+$~u', '', $value) ?? trim($value); } return trim($value, $charlist); @@ -1513,7 +1515,9 @@ public static function trim($value, $charlist = null) public static function ltrim($value, $charlist = null) { if ($charlist === null) { - return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}]+~u', '', $value) ?? ltrim($value); + $ltrimDefaultCharacters = " \n\r\t\v\0"; + + return preg_replace('~^[\s\x{FEFF}\x{200B}\x{200E}'.$ltrimDefaultCharacters.']+~u', '', $value) ?? ltrim($value); } return ltrim($value, $charlist); @@ -1529,7 +1533,9 @@ public static function ltrim($value, $charlist = null) public static function rtrim($value, $charlist = null) { if ($charlist === null) { - return preg_replace('~[\s\x{FEFF}\x{200B}\x{200E}]+$~u', '', $value) ?? rtrim($value); + $rtrimDefaultCharacters = " \n\r\t\v\0"; + + return preg_replace('~[\s\x{FEFF}\x{200B}\x{200E}'.$rtrimDefaultCharacters.']+$~u', '', $value) ?? rtrim($value); } return rtrim($value, $charlist); diff --git a/tests/Support/SupportStrTest.php b/tests/Support/SupportStrTest.php index 0901ce0b3f8e..a132b32b9500 100755 --- a/tests/Support/SupportStrTest.php +++ b/tests/Support/SupportStrTest.php @@ -861,6 +861,16 @@ public function testTrim() ); $this->assertSame("\xE9", Str::trim(" \xE9 ")); + + $trimDefaultChars = [' ', "\n", "\r", "\t", "\v", "\0"]; + + foreach ($trimDefaultChars as $char) { + $this->assertSame('', Str::trim(" {$char} ")); + $this->assertSame(trim(" {$char} "), Str::trim(" {$char} ")); + + $this->assertSame('foo bar', Str::trim("{$char} foo bar {$char}")); + $this->assertSame(trim("{$char} foo bar {$char}"), Str::trim("{$char} foo bar {$char}")); + } } public function testLtrim() @@ -881,6 +891,16 @@ public function testLtrim() ') ); $this->assertSame("\xE9 ", Str::ltrim(" \xE9 ")); + + $ltrimDefaultChars = [' ', "\n", "\r", "\t", "\v", "\0"]; + + foreach ($ltrimDefaultChars as $char) { + $this->assertSame('', Str::ltrim(" {$char} ")); + $this->assertSame(ltrim(" {$char} "), Str::ltrim(" {$char} ")); + + $this->assertSame("foo bar {$char}", Str::ltrim("{$char} foo bar {$char}")); + $this->assertSame(ltrim("{$char} foo bar {$char}"), Str::ltrim("{$char} foo bar {$char}")); + } } public function testRtrim() @@ -902,6 +922,16 @@ public function testRtrim() ); $this->assertSame(" \xE9", Str::rtrim(" \xE9 ")); + + $rtrimDefaultChars = [' ', "\n", "\r", "\t", "\v", "\0"]; + + foreach ($rtrimDefaultChars as $char) { + $this->assertSame('', Str::rtrim(" {$char} ")); + $this->assertSame(rtrim(" {$char} "), Str::rtrim(" {$char} ")); + + $this->assertSame("{$char} foo bar", Str::rtrim("{$char} foo bar {$char}")); + $this->assertSame(rtrim("{$char} foo bar {$char}"), Str::rtrim("{$char} foo bar {$char}")); + } } public function testSquish() From b7c628776754a7d861dd48ff18768e1c1f2d5f5f Mon Sep 17 00:00:00 2001 From: Kennedy Tedesco Date: Fri, 6 Sep 2024 17:53:25 -0300 Subject: [PATCH 024/100] [11.x] Add `Skip` middleware for Queue Jobs (#52645) * [11.x] Add `Skip` middleware for Queue Jobs * CS Fixes * formatting --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Queue/Middleware/Skip.php | 44 ++++++ .../Integration/Queue/SkipMiddlewareTest.php | 132 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/Illuminate/Queue/Middleware/Skip.php create mode 100644 tests/Integration/Queue/SkipMiddlewareTest.php diff --git a/src/Illuminate/Queue/Middleware/Skip.php b/src/Illuminate/Queue/Middleware/Skip.php new file mode 100644 index 000000000000..37fc91d5949f --- /dev/null +++ b/src/Illuminate/Queue/Middleware/Skip.php @@ -0,0 +1,44 @@ +skip) { + return false; + } + + return $next($job); + } +} diff --git a/tests/Integration/Queue/SkipMiddlewareTest.php b/tests/Integration/Queue/SkipMiddlewareTest.php new file mode 100644 index 000000000000..14bd1809b194 --- /dev/null +++ b/tests/Integration/Queue/SkipMiddlewareTest.php @@ -0,0 +1,132 @@ +assertJobWasSkipped($job); + } + + public function testJobIsSkippedWhenConditionIsTrueUsingClosure() + { + $job = new SkipTestJob(skip: new SerializableClosure(fn () => true)); + + $this->assertJobWasSkipped($job); + } + + public function testJobIsNotSkippedWhenConditionIsFalse() + { + $job = new SkipTestJob(skip: false); + + $this->assertJobRanSuccessfully($job); + } + + public function testJobIsNotSkippedWhenConditionIsFalseUsingClosure() + { + $job = new SkipTestJob(skip: new SerializableClosure(fn () => false)); + + $this->assertJobRanSuccessfully($job); + } + + public function testJobIsNotSkippedWhenConditionIsTrueWithUnless() + { + $job = new SkipTestJob(skip: true, useUnless: true); + + $this->assertJobRanSuccessfully($job); + } + + public function testJobIsNotSkippedWhenConditionIsTrueWithUnlessUsingClosure() + { + $job = new SkipTestJob(skip: new SerializableClosure(fn () => true), useUnless: true); + + $this->assertJobRanSuccessfully($job); + } + + public function testJobIsSkippedWhenConditionIsFalseWithUnless() + { + $job = new SkipTestJob(skip: false, useUnless: true); + + $this->assertJobWasSkipped($job); + } + + public function testJobIsSkippedWhenConditionIsFalseWithUnlessUsingClosure() + { + $job = new SkipTestJob(skip: new SerializableClosure(fn () => false), useUnless: true); + + $this->assertJobWasSkipped($job); + } + + protected function assertJobRanSuccessfully(SkipTestJob $class) + { + $this->assertJobHandled(class: $class, expectedHandledValue: true); + } + + protected function assertJobWasSkipped(SkipTestJob $class) + { + $this->assertJobHandled(class: $class, expectedHandledValue: false); + } + + protected function assertJobHandled(SkipTestJob $class, bool $expectedHandledValue) + { + $class::$handled = false; + $instance = new CallQueuedHandler(new Dispatcher($this->app), $this->app); + + $job = m::mock(Job::class); + + $job->shouldReceive('hasFailed')->andReturn(false); + $job->shouldReceive('isReleased')->andReturn(false); + $job->shouldReceive('isDeletedOrReleased')->andReturn(false); + $job->shouldReceive('delete')->once(); + + $instance->call($job, [ + 'command' => serialize($class), + ]); + + $this->assertEquals($expectedHandledValue, $class::$handled); + } +} + +class SkipTestJob +{ + use InteractsWithQueue, Queueable; + + public static $handled = false; + + public function __construct( + protected bool|SerializableClosure $skip, + protected bool $useUnless = false, + ) { + } + + public function handle(): void + { + static::$handled = true; + } + + public function middleware(): array + { + $skip = $this->skip instanceof SerializableClosure + ? $this->skip->getClosure() + : $this->skip; + + if ($this->useUnless) { + return [Skip::unless($skip)]; + } + + return [Skip::when($skip)]; + } +} From f9696a7d7fcf08b18800f47d610bc15061345295 Mon Sep 17 00:00:00 2001 From: Wouter Rutgers Date: Mon, 9 Sep 2024 15:51:02 +0200 Subject: [PATCH 025/100] fix etag headers for binary file responses (#52705) --- src/Illuminate/Http/Middleware/SetCacheHeaders.php | 2 +- tests/Http/Middleware/CacheTest.php | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Http/Middleware/SetCacheHeaders.php b/src/Illuminate/Http/Middleware/SetCacheHeaders.php index ae40ba0d98c5..99140c7b767d 100644 --- a/src/Illuminate/Http/Middleware/SetCacheHeaders.php +++ b/src/Illuminate/Http/Middleware/SetCacheHeaders.php @@ -62,7 +62,7 @@ public function handle($request, Closure $next, $options = []) } if (isset($options['etag']) && $options['etag'] === true) { - $options['etag'] = $response->getEtag() ?? md5($response->getContent()); + $options['etag'] = $response->getEtag() ?? ($response->getContent() ? md5($response->getContent()) : null); } if (isset($options['last_modified'])) { diff --git a/tests/Http/Middleware/CacheTest.php b/tests/Http/Middleware/CacheTest.php index cf238c39d706..95958446b89b 100644 --- a/tests/Http/Middleware/CacheTest.php +++ b/tests/Http/Middleware/CacheTest.php @@ -173,4 +173,13 @@ public function testTrailingDelimiterIgnored() $this->assertSame($time, $response->getLastModified()->getTimestamp()); } + + public function testItDoesNotSetEtagHeadersForBinaryContent() + { + $response = (new Cache)->handle(new Request, function () { + return new BinaryFileResponse(__DIR__.'/../fixtures/test.txt'); + }, 'etag'); + + $this->assertNull($response->getEtag()); + } } From 80cdd87be0a60bf3ff6c6a5666dd8c5c60058a44 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 9 Sep 2024 21:54:22 +0800 Subject: [PATCH 026/100] [10.x] Fixes `whereDate`, `whereDay`, `whereMonth`, `whereTime`, `whereYear` and `whereJsonLength` to ignore invalid `$operator` (#52704) * Fixes `whereDate`, `whereDay`, `whereMonth`, `whereTime`, `whereYear` and `whereJsonLength` to ignore invalid `$operator` 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 * 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 --- .../Database/DBAL/TimestampType.php | 10 +- src/Illuminate/Database/Query/Builder.php | 42 ++++ .../Integration/Database/QueryBuilderTest.php | 208 ++++++++++++++++++ 3 files changed, 258 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/DBAL/TimestampType.php b/src/Illuminate/Database/DBAL/TimestampType.php index b5d9777503d9..e344dd919ae8 100644 --- a/src/Illuminate/Database/DBAL/TimestampType.php +++ b/src/Illuminate/Database/DBAL/TimestampType.php @@ -4,14 +4,17 @@ use Doctrine\DBAL\Exception as DBALException; use Doctrine\DBAL\Platforms\AbstractPlatform; +use Doctrine\DBAL\Platforms\MariaDb1010Platform; 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; +use Doctrine\DBAL\Platforms\MySQL84Platform; use Doctrine\DBAL\Platforms\MySQLPlatform; use Doctrine\DBAL\Platforms\PostgreSQL100Platform; +use Doctrine\DBAL\Platforms\PostgreSQL120Platform; use Doctrine\DBAL\Platforms\PostgreSQL94Platform; use Doctrine\DBAL\Platforms\PostgreSQLPlatform; use Doctrine\DBAL\Platforms\SqlitePlatform; @@ -33,13 +36,16 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st MySQLPlatform::class, MySQL57Platform::class, MySQL80Platform::class, + MySQL84Platform::class, MariaDBPlatform::class, MariaDb1027Platform::class, MariaDb1052Platform::class, - MariaDb1060Platform::class => $this->getMySqlPlatformSQLDeclaration($column), + MariaDb1060Platform::class, + MariaDb1010Platform::class => $this->getMySqlPlatformSQLDeclaration($column), PostgreSQLPlatform::class, PostgreSQL94Platform::class, - PostgreSQL100Platform::class => $this->getPostgresPlatformSQLDeclaration($column), + PostgreSQL100Platform::class, + PostgreSQL120Platform::class => $this->getPostgresPlatformSQLDeclaration($column), SQLServerPlatform::class, SQLServer2012Platform::class => $this->getSqlServerPlatformSQLDeclaration($column), SqlitePlatform::class => 'DATETIME', diff --git a/src/Illuminate/Database/Query/Builder.php b/src/Illuminate/Database/Query/Builder.php index 9f87562f01d6..52b38a6d2902 100755 --- a/src/Illuminate/Database/Query/Builder.php +++ b/src/Illuminate/Database/Query/Builder.php @@ -1436,6 +1436,13 @@ public function whereDate($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { @@ -1477,6 +1484,13 @@ public function whereTime($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { @@ -1518,6 +1532,13 @@ public function whereDay($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { @@ -1563,6 +1584,13 @@ public function whereMonth($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { @@ -1608,6 +1636,13 @@ public function whereYear($column, $operator, $value = null, $boolean = 'and') $value, $operator, func_num_args() === 2 ); + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + $value = $this->flattenValue($value); if ($value instanceof DateTimeInterface) { @@ -1974,6 +2009,13 @@ public function whereJsonLength($column, $operator, $value = null, $boolean = 'a $value, $operator, func_num_args() === 2 ); + // If the given operator is not found in the list of valid operators we will + // assume that the developer is just short-cutting the '=' operators and + // we will set the operators to '=' and set the values appropriately. + if ($this->invalidOperator($operator)) { + [$value, $operator] = [$operator, '=']; + } + $this->wheres[] = compact('type', 'column', 'operator', 'value', 'boolean'); if (! $value instanceof ExpressionContract) { diff --git a/tests/Integration/Database/QueryBuilderTest.php b/tests/Integration/Database/QueryBuilderTest.php index 98ae0792b212..c323ff0e1513 100644 --- a/tests/Integration/Database/QueryBuilderTest.php +++ b/tests/Integration/Database/QueryBuilderTest.php @@ -9,6 +9,9 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use Illuminate\Testing\Assert as PHPUnit; +use Orchestra\Testbench\Attributes\DefineEnvironment; +use PDOException; class QueryBuilderTest extends DatabaseTestCase { @@ -305,12 +308,52 @@ public function testWhereDate() $this->assertSame(1, DB::table('posts')->whereDate('created_at', new Carbon('2018-01-02'))->count()); } + #[DefineEnvironment('defineEnvironmentWouldThrowsPDOException')] + public function testWhereDateWithInvalidOperator() + { + $sql = DB::table('posts')->whereDate('created_at', '? OR 1=1', '2018-01-02'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'created_at', + 'type' => 'Date', + 'value' => '? OR 1=1', + 'boolean' => 'and', + ], + ], $sql->wheres); + + $this->assertSame(0, $sql->count()); + } + public function testOrWhereDate() { $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereDate('created_at', '2018-01-02')->count()); $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereDate('created_at', new Carbon('2018-01-02'))->count()); } + #[DefineEnvironment('defineEnvironmentWouldThrowsPDOException')] + public function testOrWhereDateWithInvalidOperator() + { + $sql = DB::table('posts')->where('id', 1)->orWhereDate('created_at', '? OR 1=1', '2018-01-02'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'id', + 'type' => 'Basic', + 'value' => 1, + 'boolean' => 'and', + ], + [ + 'column' => 'created_at', + 'type' => 'Date', + 'value' => '? OR 1=1', + 'boolean' => 'or', + ], + ], $sql->wheres); + + $this->assertSame(1, $sql->count()); + } + public function testWhereDay() { $this->assertSame(1, DB::table('posts')->whereDay('created_at', '02')->count()); @@ -318,6 +361,22 @@ public function testWhereDay() $this->assertSame(1, DB::table('posts')->whereDay('created_at', new Carbon('2018-01-02'))->count()); } + public function testWhereDayWithInvalidOperator() + { + $sql = DB::table('posts')->whereDay('created_at', '? OR 1=1', '02'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'created_at', + 'type' => 'Day', + 'value' => '00', + 'boolean' => 'and', + ], + ], $sql->wheres); + + $this->assertSame(0, $sql->count()); + } + public function testOrWhereDay() { $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereDay('created_at', '02')->count()); @@ -325,6 +384,28 @@ public function testOrWhereDay() $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereDay('created_at', new Carbon('2018-01-02'))->count()); } + public function testOrWhereDayWithInvalidOperator() + { + $sql = DB::table('posts')->where('id', 1)->orWhereDay('created_at', '? OR 1=1', '02'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'id', + 'type' => 'Basic', + 'value' => 1, + 'boolean' => 'and', + ], + [ + 'column' => 'created_at', + 'type' => 'Day', + 'value' => '00', + 'boolean' => 'or', + ], + ], $sql->wheres); + + $this->assertSame(1, $sql->count()); + } + public function testWhereMonth() { $this->assertSame(1, DB::table('posts')->whereMonth('created_at', '01')->count()); @@ -332,6 +413,22 @@ public function testWhereMonth() $this->assertSame(1, DB::table('posts')->whereMonth('created_at', new Carbon('2018-01-02'))->count()); } + public function testWhereMonthWithInvalidOperator() + { + $sql = DB::table('posts')->whereMonth('created_at', '? OR 1=1', '01'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'created_at', + 'type' => 'Month', + 'value' => '00', + 'boolean' => 'and', + ], + ], $sql->wheres); + + $this->assertSame(0, $sql->count()); + } + public function testOrWhereMonth() { $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereMonth('created_at', '01')->count()); @@ -339,6 +436,28 @@ public function testOrWhereMonth() $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereMonth('created_at', new Carbon('2018-01-02'))->count()); } + public function testOrWhereMonthWithInvalidOperator() + { + $sql = DB::table('posts')->where('id', 1)->orWhereMonth('created_at', '? OR 1=1', '01'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'id', + 'type' => 'Basic', + 'value' => 1, + 'boolean' => 'and', + ], + [ + 'column' => 'created_at', + 'type' => 'Month', + 'value' => '00', + 'boolean' => 'or', + ], + ], $sql->wheres); + + $this->assertSame(1, $sql->count()); + } + public function testWhereYear() { $this->assertSame(1, DB::table('posts')->whereYear('created_at', '2018')->count()); @@ -346,6 +465,23 @@ public function testWhereYear() $this->assertSame(1, DB::table('posts')->whereYear('created_at', new Carbon('2018-01-02'))->count()); } + #[DefineEnvironment('defineEnvironmentWouldThrowsPDOException')] + public function testWhereYearWithInvalidOperator() + { + $sql = DB::table('posts')->whereYear('created_at', '? OR 1=1', '2018'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'created_at', + 'type' => 'Year', + 'value' => '? OR 1=1', + 'boolean' => 'and', + ], + ], $sql->wheres); + + $this->assertSame(0, $sql->count()); + } + public function testOrWhereYear() { $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereYear('created_at', '2018')->count()); @@ -353,18 +489,81 @@ public function testOrWhereYear() $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereYear('created_at', new Carbon('2018-01-02'))->count()); } + #[DefineEnvironment('defineEnvironmentWouldThrowsPDOException')] + public function testOrWhereYearWithInvalidOperator() + { + $sql = DB::table('posts')->where('id', 1)->orWhereYear('created_at', '? OR 1=1', '2018'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'id', + 'type' => 'Basic', + 'value' => 1, + 'boolean' => 'and', + ], + [ + 'column' => 'created_at', + 'type' => 'Year', + 'value' => '? OR 1=1', + 'boolean' => 'or', + ], + ], $sql->wheres); + + $this->assertSame(1, $sql->count()); + } + public function testWhereTime() { $this->assertSame(1, DB::table('posts')->whereTime('created_at', '03:04:05')->count()); $this->assertSame(1, DB::table('posts')->whereTime('created_at', new Carbon('2018-01-02 03:04:05'))->count()); } + #[DefineEnvironment('defineEnvironmentWouldThrowsPDOException')] + public function testWhereTimeWithInvalidOperator() + { + $sql = DB::table('posts')->whereTime('created_at', '? OR 1=1', '03:04:05'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'created_at', + 'type' => 'Time', + 'value' => '? OR 1=1', + 'boolean' => 'and', + ], + ], $sql->wheres); + + $this->assertSame(0, $sql->count()); + } + public function testOrWhereTime() { $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereTime('created_at', '03:04:05')->count()); $this->assertSame(2, DB::table('posts')->where('id', 1)->orWhereTime('created_at', new Carbon('2018-01-02 03:04:05'))->count()); } + #[DefineEnvironment('defineEnvironmentWouldThrowsPDOException')] + public function testOrWhereTimeWithInvalidOperator() + { + $sql = DB::table('posts')->where('id', 1)->orWhereTime('created_at', '? OR 1=1', '03:04:05'); + + PHPUnit::assertArraySubset([ + [ + 'column' => 'id', + 'type' => 'Basic', + 'value' => 1, + 'boolean' => 'and', + ], + [ + 'column' => 'created_at', + 'type' => 'Time', + 'value' => '? OR 1=1', + 'boolean' => 'or', + ], + ], $sql->wheres); + + $this->assertSame(1, $sql->count()); + } + public function testWhereNested() { $results = DB::table('posts')->where('content', 'Lorem Ipsum.')->whereNested(function ($query) { @@ -436,4 +635,13 @@ public function testPluck() 'Lorem Ipsum.' => 'Bar Post', ], DB::table('posts')->pluck('title', 'content')->toArray()); } + + protected function defineEnvironmentWouldThrowsPDOException($app) + { + $this->afterApplicationCreated(function () { + if (in_array($this->driver, ['pgsql', 'sqlsrv'])) { + $this->expectException(PDOException::class); + } + }); + } } From 0891d0f83b59ba7b35436d0c95300ffa0bd911e2 Mon Sep 17 00:00:00 2001 From: Kennedy Tedesco Date: Mon, 9 Sep 2024 10:56:35 -0300 Subject: [PATCH 027/100] [11.x] add `withoutDelay()` to PendingDispatch (#52696) --- src/Illuminate/Foundation/Bus/PendingDispatch.php | 12 ++++++++++++ tests/Queue/QueueDelayTest.php | 11 +++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index 1e514e5b0b78..ae5136c60b6d 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -100,6 +100,18 @@ public function delay($delay) return $this; } + /** + * Set the delay for the job to zero seconds. + * + * @return $this + */ + public function withoutDelay() + { + $this->job->withoutDelay(); + + return $this; + } + /** * Indicate that the job should be dispatched after all database transactions have committed. * diff --git a/tests/Queue/QueueDelayTest.php b/tests/Queue/QueueDelayTest.php index 9e3d1454a477..3cea13a5090e 100644 --- a/tests/Queue/QueueDelayTest.php +++ b/tests/Queue/QueueDelayTest.php @@ -30,6 +30,17 @@ public function test_queue_without_delay() $this->assertEquals(0, $job->delay); } + + public function test_pending_dispatch_without_delay() + { + Queue::fake(); + + $job = new TestJob; + + dispatch($job)->withoutDelay(); + + $this->assertEquals(0, $job->delay); + } } class TestJob implements ShouldQueue From 9587bb22db4fb235eb154a156688b6a421267606 Mon Sep 17 00:00:00 2001 From: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:56:48 +0400 Subject: [PATCH 028/100] Refactor getInstance method to use null coalescing assignment (#52693) Co-authored-by: Xurshudyan --- src/Illuminate/Container/Container.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 9837bc47f991..829b797404b1 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -1519,11 +1519,7 @@ public function flush() */ public static function getInstance() { - if (is_null(static::$instance)) { - static::$instance = new static; - } - - return static::$instance; + return static::$instance ??= new static; } /** From 5010f41815f32e25eb8d13148b18f5e4b8de564b Mon Sep 17 00:00:00 2001 From: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:57:06 +0400 Subject: [PATCH 029/100] Removed unnecessary call to setAccessible(true) (#52691) Co-authored-by: Xurshudyan --- .../Foundation/Exceptions/Renderer/Mappers/BladeMapper.php | 3 --- tests/Foundation/Configuration/MiddlewareTest.php | 6 ------ tests/View/ViewComponentTest.php | 2 -- 3 files changed, 11 deletions(-) diff --git a/src/Illuminate/Foundation/Exceptions/Renderer/Mappers/BladeMapper.php b/src/Illuminate/Foundation/Exceptions/Renderer/Mappers/BladeMapper.php index f0a540e9788e..8a4da94a3db5 100644 --- a/src/Illuminate/Foundation/Exceptions/Renderer/Mappers/BladeMapper.php +++ b/src/Illuminate/Foundation/Exceptions/Renderer/Mappers/BladeMapper.php @@ -124,14 +124,11 @@ protected function getKnownPaths() if (! $compilerEngineReflection->hasProperty('lastCompiled') && $compilerEngineReflection->hasProperty('engine')) { $compilerEngine = $compilerEngineReflection->getProperty('engine'); - $compilerEngine->setAccessible(true); $compilerEngine = $compilerEngine->getValue($bladeCompilerEngine); $lastCompiled = new ReflectionProperty($compilerEngine, 'lastCompiled'); - $lastCompiled->setAccessible(true); $lastCompiled = $lastCompiled->getValue($compilerEngine); } else { $lastCompiled = $compilerEngineReflection->getProperty('lastCompiled'); - $lastCompiled->setAccessible(true); $lastCompiled = $lastCompiled->getValue($bladeCompilerEngine); } diff --git a/tests/Foundation/Configuration/MiddlewareTest.php b/tests/Foundation/Configuration/MiddlewareTest.php index d4ede8561a05..3971b895cac1 100644 --- a/tests/Foundation/Configuration/MiddlewareTest.php +++ b/tests/Foundation/Configuration/MiddlewareTest.php @@ -132,10 +132,7 @@ public function testTrustProxies() $reflection = new ReflectionClass($middleware); $method = $reflection->getMethod('proxies'); - $method->setAccessible(true); - $property = $reflection->getProperty('proxies'); - $property->setAccessible(true); $this->assertNull($method->invoke($middleware)); @@ -169,10 +166,7 @@ public function testTrustHeaders() $reflection = new ReflectionClass($middleware); $method = $reflection->getMethod('headers'); - $method->setAccessible(true); - $property = $reflection->getProperty('headers'); - $property->setAccessible(true); $this->assertEquals(Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | diff --git a/tests/View/ViewComponentTest.php b/tests/View/ViewComponentTest.php index 9057b6714b7e..e4e3701bbaa5 100644 --- a/tests/View/ViewComponentTest.php +++ b/tests/View/ViewComponentTest.php @@ -49,8 +49,6 @@ public function goodbye() $reflectionMethod = new ReflectionMethod($component, 'ignoredMethods'); - $reflectionMethod->setAccessible(true); - $ignoredMethods = $reflectionMethod->invoke($component); foreach ($ignoredMethods as $method) { From fc4918d99d59b1bead851b51fcd8218221718657 Mon Sep 17 00:00:00 2001 From: Steve Bauman Date: Mon, 9 Sep 2024 10:15:22 -0400 Subject: [PATCH 030/100] [11.x] Add `Eloquent\Collection::findOrFail` (#52690) * Add findOrFail to Eloquent Collection * Add tests --- .../Database/Eloquent/Collection.php | 31 +++++++++++ .../DatabaseEloquentCollectionTest.php | 54 +++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index 9640e41c540b..7fe826d165b1 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -50,6 +50,37 @@ public function find($key, $default = null) return Arr::first($this->items, fn ($model) => $model->getKey() == $key, $default); } + /** + * Find a model in the collection by key or throw an exception. + * + * @param mixed $key + * @return TModel + * + * @throws \Illuminate\Database\Eloquent\ModelNotFoundException + */ + public function findOrFail($key) + { + $result = $this->find($key); + + if (is_array($key) && count($result) === count(array_unique($key))) { + return $result; + } elseif (! is_array($key) && ! is_null($result)) { + return $result; + } + + $exception = new ModelNotFoundException; + + if (! $model = head($this->items)) { + throw $exception; + } + + $ids = is_array($key) ? array_diff($key, $result->modelKeys()) : $key; + + $exception->setModel(get_class($model), $ids); + + throw $exception; + } + /** * Load a set of relationships onto the collection. * diff --git a/tests/Database/DatabaseEloquentCollectionTest.php b/tests/Database/DatabaseEloquentCollectionTest.php index d5e9d64652d3..7b9b6655980f 100755 --- a/tests/Database/DatabaseEloquentCollectionTest.php +++ b/tests/Database/DatabaseEloquentCollectionTest.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model as Eloquent; +use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Support\Collection as BaseCollection; use LogicException; use Mockery as m; @@ -202,6 +203,59 @@ public function testFindMethodFindsManyModelsById() $this->assertEquals([2, 3], $c->find([2, 3, 4])->pluck('id')->all()); } + public function testFindOrFailFindsModelById() + { + $mockModel = m::mock(Model::class); + $mockModel->shouldReceive('getKey')->andReturn(1); + $c = new Collection([$mockModel]); + + $this->assertSame($mockModel, $c->findOrFail(1)); + } + + public function testFindOrFailFindsManyModelsById() + { + $model1 = (new TestEloquentCollectionModel)->forceFill(['id' => 1]); + $model2 = (new TestEloquentCollectionModel)->forceFill(['id' => 2]); + + $c = new Collection; + $this->assertInstanceOf(Collection::class, $c->findOrFail([])); + $this->assertCount(0, $c->findOrFail([])); + + $c->push($model1); + $this->assertCount(1, $c->findOrFail([1])); + $this->assertEquals(1, $c->findOrFail([1])->first()->id); + + $c->push($model2); + $this->assertCount(2, $c->findOrFail([1, 2])); + + $this->expectException(ModelNotFoundException::class); + $this->expectExceptionMessage('No query results for model [Illuminate\Tests\Database\TestEloquentCollectionModel] 3'); + + $c->findOrFail([1, 2, 3]); + } + + public function testFindOrFailThrowsExceptionWithMessageWhenOtherModelsArePresent() + { + $model = (new TestEloquentCollectionModel)->forceFill(['id' => 1]); + + $c = new Collection([$model]); + + $this->expectException(ModelNotFoundException::class); + $this->expectExceptionMessage('No query results for model [Illuminate\Tests\Database\TestEloquentCollectionModel] 2'); + + $c->findOrFail(2); + } + + public function testFindOrFailThrowsExceptionWithoutMessageWhenOtherModelsAreNotPresent() + { + $c = new Collection(); + + $this->expectException(ModelNotFoundException::class); + $this->expectExceptionMessage(''); + + $c->findOrFail(1); + } + public function testLoadMethodEagerLoadsGivenRelationships() { $c = $this->getMockBuilder(Collection::class)->onlyMethods(['first'])->setConstructorArgs([['foo']])->getMock(); From bd1ca2befc9861820d61dac8a880b7b809947d5b Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 9 Sep 2024 09:45:14 -0500 Subject: [PATCH 031/100] firstOrFail on query builder --- .../Database/Concerns/BuildsQueries.php | 19 ++++++++++++++ .../Database/RecordNotFoundException.php | 10 ++++++++ .../Foundation/Exceptions/Handler.php | 5 +++- .../View/Engines/CompilerEngine.php | 2 ++ tests/Database/DatabaseQueryBuilderTest.php | 25 +++++++++++++++++++ 5 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 src/Illuminate/Database/RecordNotFoundException.php diff --git a/src/Illuminate/Database/Concerns/BuildsQueries.php b/src/Illuminate/Database/Concerns/BuildsQueries.php index 4d401125fb0c..bb5d4e86cacb 100644 --- a/src/Illuminate/Database/Concerns/BuildsQueries.php +++ b/src/Illuminate/Database/Concerns/BuildsQueries.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\MultipleRecordsFoundException; use Illuminate\Database\Query\Expression; +use Illuminate\Database\RecordNotFoundException; use Illuminate\Database\RecordsNotFoundException; use Illuminate\Pagination\Cursor; use Illuminate\Pagination\CursorPaginator; @@ -343,6 +344,24 @@ public function first($columns = ['*']) return $this->take(1)->get($columns)->first(); } + /** + * Execute the query and get the first result or throw an exception. + * + * @param array|string $columns + * @param string|null $message + * @return TValue + * + * @throws \Illuminate\Database\RecordNotFoundException + */ + public function firstOrFail($columns = ['*'], $message = null) + { + if (! is_null($result = $this->first($columns))) { + return $result; + } + + throw new RecordNotFoundException($message ?: 'No record found for the given query.'); + } + /** * Execute the query and get the first result if it's the sole matching record. * diff --git a/src/Illuminate/Database/RecordNotFoundException.php b/src/Illuminate/Database/RecordNotFoundException.php new file mode 100644 index 000000000000..3a717feeed24 --- /dev/null +++ b/src/Illuminate/Database/RecordNotFoundException.php @@ -0,0 +1,10 @@ +hasStatus() => new AccessDeniedHttpException($e->getMessage(), $e), $e instanceof TokenMismatchException => new HttpException(419, $e->getMessage(), $e), $e instanceof RequestExceptionInterface => new BadRequestHttpException('Bad request.', $e), + $e instanceof RecordNotFoundException => new NotFoundHttpException('Not found.', $e), $e instanceof RecordsNotFoundException => new NotFoundHttpException('Not found.', $e), default => $e, }; diff --git a/src/Illuminate/View/Engines/CompilerEngine.php b/src/Illuminate/View/Engines/CompilerEngine.php index 82953f1dc8e5..745300d65935 100755 --- a/src/Illuminate/View/Engines/CompilerEngine.php +++ b/src/Illuminate/View/Engines/CompilerEngine.php @@ -2,6 +2,7 @@ namespace Illuminate\View\Engines; +use Illuminate\Database\RecordNotFoundException; use Illuminate\Database\RecordsNotFoundException; use Illuminate\Filesystem\Filesystem; use Illuminate\Http\Exceptions\HttpResponseException; @@ -105,6 +106,7 @@ protected function handleViewException(Throwable $e, $obLevel) { if ($e instanceof HttpException || $e instanceof HttpResponseException || + $e instanceof RecordNotFoundException || $e instanceof RecordsNotFoundException) { parent::handleViewException($e, $obLevel); } diff --git a/tests/Database/DatabaseQueryBuilderTest.php b/tests/Database/DatabaseQueryBuilderTest.php index f12f27f0e26b..18ab730f2f69 100755 --- a/tests/Database/DatabaseQueryBuilderTest.php +++ b/tests/Database/DatabaseQueryBuilderTest.php @@ -20,6 +20,7 @@ use Illuminate\Database\Query\Processors\MySqlProcessor; use Illuminate\Database\Query\Processors\PostgresProcessor; use Illuminate\Database\Query\Processors\Processor; +use Illuminate\Database\RecordNotFoundException; use Illuminate\Pagination\AbstractPaginator as Paginator; use Illuminate\Pagination\Cursor; use Illuminate\Pagination\CursorPaginator; @@ -2992,6 +2993,30 @@ public function testFirstMethodReturnsFirstResult() $this->assertEquals(['foo' => 'bar'], $results); } + public function testFirstOrFailMethodReturnsFirstResult() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with('select * from "users" where "id" = ? limit 1', [1], true)->andReturn([['foo' => 'bar']]); + $builder->getProcessor()->shouldReceive('processSelect')->once()->with($builder, [['foo' => 'bar']])->andReturnUsing(function ($query, $results) { + return $results; + }); + $results = $builder->from('users')->where('id', '=', 1)->firstOrFail(); + $this->assertEquals(['foo' => 'bar'], $results); + } + + public function testFirstOrFailMethodThrowsRecordNotFoundException() + { + $builder = $this->getBuilder(); + $builder->getConnection()->shouldReceive('select')->once()->with('select * from "users" where "id" = ? limit 1', [1], true)->andReturn([]); + + $builder->getProcessor()->shouldReceive('processSelect')->once()->with($builder, [])->andReturn([]); + + $this->expectException(RecordNotFoundException::class); + $this->expectExceptionMessage('No record found for the given query.'); + + $builder->from('users')->where('id', '=', 1)->firstOrFail(); + } + public function testPluckMethodGetsCollectionOfColumnValues() { $builder = $this->getBuilder(); From 44315db5369763a7ae0c2b3bf5142850ffe34e91 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Mon, 9 Sep 2024 14:45:36 +0000 Subject: [PATCH 032/100] Apply fixes from StyleCI --- src/Illuminate/Foundation/Exceptions/Handler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Exceptions/Handler.php b/src/Illuminate/Foundation/Exceptions/Handler.php index 81d94c72bc06..ec3ec9c2187c 100644 --- a/src/Illuminate/Foundation/Exceptions/Handler.php +++ b/src/Illuminate/Foundation/Exceptions/Handler.php @@ -36,8 +36,8 @@ use Illuminate\Support\ViewErrorBag; use Illuminate\Validation\ValidationException; use InvalidArgumentException; -use Psr\Log\LogLevel; use Psr\Log\LoggerInterface; +use Psr\Log\LogLevel; use Symfony\Component\Console\Application as ConsoleApplication; use Symfony\Component\Console\Exception\CommandNotFoundException; use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer; From a2c66ad997d583560836ae1c578bbbba6d52e26b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Mon, 9 Sep 2024 23:32:46 +0800 Subject: [PATCH 033/100] [11.x] Static Analysis only ignore PHPStan error for (#52712) `staticMethod.notFound` on Facade usage. Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php | 2 +- src/Illuminate/Log/Context/ContextServiceProvider.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php index 31564c1f56a8..f3bb5fd11f08 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php @@ -1400,7 +1400,7 @@ protected function castAttributeAsHashedString($key, #[\SensitiveParameter] $val return Hash::make($value); } - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore staticMethod.notFound */ if (! Hash::verifyConfiguration($value)) { throw new RuntimeException("Could not verify the hashed value's configuration."); } diff --git a/src/Illuminate/Log/Context/ContextServiceProvider.php b/src/Illuminate/Log/Context/ContextServiceProvider.php index 4e65515ca4c7..7c00e256ae26 100644 --- a/src/Illuminate/Log/Context/ContextServiceProvider.php +++ b/src/Illuminate/Log/Context/ContextServiceProvider.php @@ -27,7 +27,7 @@ public function register() public function boot() { Queue::createPayloadUsing(function ($connection, $queue, $payload) { - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore staticMethod.notFound */ $context = Context::dehydrate(); return $context === null ? $payload : [ @@ -37,7 +37,7 @@ public function boot() }); $this->app['events']->listen(function (JobProcessing $event) { - /** @phpstan-ignore-next-line */ + /** @phpstan-ignore staticMethod.notFound */ Context::hydrate($event->job->payload()['illuminate:log:context'] ?? null); }); } From 77b9d8e175432e2b8aa01ee830286c0fd9667ced Mon Sep 17 00:00:00 2001 From: Jonas Staudenmeir Date: Tue, 10 Sep 2024 15:11:54 +0200 Subject: [PATCH 034/100] Fix Collection PHPDoc (#52724) --- .../Database/Eloquent/Collection.php | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index 7fe826d165b1..bdce34b0352e 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -84,7 +84,7 @@ public function findOrFail($key) /** * Load a set of relationships onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function load($relations) @@ -105,7 +105,7 @@ public function load($relations) /** * Load a set of aggregations over relationship's column onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @param string|null $function * @return $this @@ -142,7 +142,7 @@ public function loadAggregate($relations, $column, $function = null) /** * Load a set of relationship counts onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadCount($relations) @@ -153,7 +153,7 @@ public function loadCount($relations) /** * Load a set of relationship's max column values onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -165,7 +165,7 @@ public function loadMax($relations, $column) /** * Load a set of relationship's min column values onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -177,7 +177,7 @@ public function loadMin($relations, $column) /** * Load a set of relationship's column summations onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -189,7 +189,7 @@ public function loadSum($relations, $column) /** * Load a set of relationship's average column values onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -201,7 +201,7 @@ public function loadAvg($relations, $column) /** * Load a set of related existences onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadExists($relations) @@ -212,7 +212,7 @@ public function loadExists($relations) /** * Load a set of relationships onto the collection if they are not already eager loaded. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadMissing($relations) @@ -284,7 +284,7 @@ protected function loadMissingRelation(self $models, array $path) * Load a set of relationships onto the mixed relationship collection. * * @param string $relation - * @param array): mixed)|string> $relations + * @param array): mixed)|string> $relations * @return $this */ public function loadMorph($relation, $relations) @@ -301,7 +301,7 @@ public function loadMorph($relation, $relations) * Load a set of relationship counts onto the mixed relationship collection. * * @param string $relation - * @param array): mixed)|string> $relations + * @param array): mixed)|string> $relations * @return $this */ public function loadMorphCount($relation, $relations) From 15cf063d9ba1979c26f30975f1bf4b7999b30c56 Mon Sep 17 00:00:00 2001 From: Johnson Page Date: Tue, 10 Sep 2024 23:12:45 +1000 Subject: [PATCH 035/100] [11.x] Add optional parameter for `confirmed` validator rule (#52722) * Add ability to set confirmed:fieldName * Fix style --- src/Illuminate/Validation/Concerns/ValidatesAttributes.php | 5 +++-- tests/Validation/ValidationValidatorTest.php | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index b2727d8f9bb1..d97aed643a07 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -493,11 +493,12 @@ public function validateBoolean($attribute, $value) * * @param string $attribute * @param mixed $value + * @param array{0: string} $parameters * @return bool */ - public function validateConfirmed($attribute, $value) + public function validateConfirmed($attribute, $value, $parameters) { - return $this->validateSame($attribute, $value, [$attribute.'_confirmation']); + return $this->validateSame($attribute, $value, [$parameters[0] ?: $attribute.'_confirmation']); } /** diff --git a/tests/Validation/ValidationValidatorTest.php b/tests/Validation/ValidationValidatorTest.php index ff4d156cb36c..b557c94ef995 100755 --- a/tests/Validation/ValidationValidatorTest.php +++ b/tests/Validation/ValidationValidatorTest.php @@ -2124,6 +2124,12 @@ public function testValidateConfirmed() $v = new Validator($trans, ['password' => '1e2', 'password_confirmation' => '100'], ['password' => 'Confirmed']); $this->assertFalse($v->passes()); + + $v = new Validator($trans, ['password' => 'foo', 'passwordConfirmation' => 'foo'], ['password' => 'Confirmed:passwordConfirmation']); + $this->assertTrue($v->passes()); + + $v = new Validator($trans, ['password' => 'foo', 'passwordConfirmation' => 'bar'], ['password' => 'Confirmed:passwordConfirmation']); + $this->assertFalse($v->passes()); } public function testValidateSame() From 9fdf85c03a56659df291aebdbc5a0922f8da9bbe Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 10 Sep 2024 21:13:09 +0800 Subject: [PATCH 036/100] [11.x] Test Improvements (#52718) Signed-off-by: Mior Muhammad Zaki --- tests/Bus/QueueableTest.php | 17 +++++------------ tests/Support/SupportCollectionTest.php | 6 ------ 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/tests/Bus/QueueableTest.php b/tests/Bus/QueueableTest.php index a9026bca70e8..ab2e161cb8fa 100644 --- a/tests/Bus/QueueableTest.php +++ b/tests/Bus/QueueableTest.php @@ -3,6 +3,7 @@ namespace Illuminate\Tests\Bus; use Illuminate\Bus\Queueable; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\TestCase; class QueueableTest extends TestCase @@ -17,9 +18,7 @@ public static function connectionDataProvider(): array ]; } - /** - * @dataProvider connectionDataProvider - */ + #[DataProvider('connectionDataProvider')] public function testOnConnection(mixed $connection, ?string $expected): void { $job = new FakeJob(); @@ -28,9 +27,7 @@ public function testOnConnection(mixed $connection, ?string $expected): void $this->assertSame($job->connection, $expected); } - /** - * @dataProvider connectionDataProvider - */ + #[DataProvider('connectionDataProvider')] public function testAllOnConnection(mixed $connection, ?string $expected): void { $job = new FakeJob(); @@ -50,9 +47,7 @@ public static function queuesDataProvider(): array ]; } - /** - * @dataProvider queuesDataProvider - */ + #[DataProvider('queuesDataProvider')] public function testOnQueue(mixed $queue, ?string $expected): void { $job = new FakeJob(); @@ -61,9 +56,7 @@ public function testOnQueue(mixed $queue, ?string $expected): void $this->assertSame($job->queue, $expected); } - /** - * @dataProvider queuesDataProvider - */ + #[DataProvider('queuesDataProvider')] public function testAllOnQueue(mixed $queue, ?string $expected): void { $job = new FakeJob(); diff --git a/tests/Support/SupportCollectionTest.php b/tests/Support/SupportCollectionTest.php index e9e0b63979ac..ef0a5fd68f42 100755 --- a/tests/Support/SupportCollectionTest.php +++ b/tests/Support/SupportCollectionTest.php @@ -2003,9 +2003,6 @@ public function testSortByMany($collection) } #[DataProvider('collectionClassProvider')] - /** - * @dataProvider collectionClassProvider - */ public function testNaturalSortByManyWithNull($collection) { $itemFoo = new \stdClass(); @@ -2026,9 +2023,6 @@ public function testNaturalSortByManyWithNull($collection) } #[DataProvider('collectionClassProvider')] - /** - * @dataProvider collectionClassProvider - */ public function testSortKeys($collection) { $data = new $collection(['b' => 'dayle', 'a' => 'taylor']); From 95d7c12be7dd032f87f7b3615d450e6399612ae5 Mon Sep 17 00:00:00 2001 From: "Kay W." Date: Tue, 10 Sep 2024 21:13:25 +0800 Subject: [PATCH 037/100] Fix incorrect variable-length argument (#52719) --- src/Illuminate/Auth/Middleware/Authenticate.php | 2 +- src/Illuminate/Routing/Router.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Auth/Middleware/Authenticate.php b/src/Illuminate/Auth/Middleware/Authenticate.php index ef1a3f2ea030..95a16268b9ca 100644 --- a/src/Illuminate/Auth/Middleware/Authenticate.php +++ b/src/Illuminate/Auth/Middleware/Authenticate.php @@ -52,7 +52,7 @@ public static function using($guard, ...$others) * * @param \Illuminate\Http\Request $request * @param \Closure $next - * @param string[] ...$guards + * @param string ...$guards * @return mixed * * @throws \Illuminate\Auth\AuthenticationException diff --git a/src/Illuminate/Routing/Router.php b/src/Illuminate/Routing/Router.php index d3085e69a57e..2be5d05e3a14 100644 --- a/src/Illuminate/Routing/Router.php +++ b/src/Illuminate/Routing/Router.php @@ -1344,7 +1344,7 @@ public function currentRouteAction() /** * Alias for the "currentRouteUses" method. * - * @param array ...$patterns + * @param array|string ...$patterns * @return bool */ public function uses(...$patterns) From 32ecbcdedf1dd7faac94760ba22189e0a6b06f18 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Tue, 10 Sep 2024 13:15:52 +0000 Subject: [PATCH 038/100] Update facade docblocks --- src/Illuminate/Support/Facades/Route.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Route.php b/src/Illuminate/Support/Facades/Route.php index c24273bfe19d..e108078f3a68 100755 --- a/src/Illuminate/Support/Facades/Route.php +++ b/src/Illuminate/Support/Facades/Route.php @@ -65,7 +65,7 @@ * @method static bool is(mixed ...$patterns) * @method static bool currentRouteNamed(mixed ...$patterns) * @method static string|null currentRouteAction() - * @method static bool uses(array ...$patterns) + * @method static bool uses(array|string ...$patterns) * @method static bool currentRouteUses(string $action) * @method static void singularResourceParameters(bool $singular = true) * @method static void resourceParameters(array $parameters = []) From 89a2a213e4df5b83719238ecbad378ae8ba4bf8b Mon Sep 17 00:00:00 2001 From: Shea Lavington Date: Tue, 10 Sep 2024 14:17:52 +0100 Subject: [PATCH 039/100] Allow testing of relative signed routes (#52726) * Allow testing of relative signed routes * Fix styling --- src/Illuminate/Testing/TestResponse.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Testing/TestResponse.php b/src/Illuminate/Testing/TestResponse.php index 5f361a6dc754..d1958590914b 100644 --- a/src/Illuminate/Testing/TestResponse.php +++ b/src/Illuminate/Testing/TestResponse.php @@ -236,9 +236,10 @@ public function assertRedirectToRoute($name, $parameters = []) * * @param string|null $name * @param mixed $parameters + * @param bool $absolute * @return $this */ - public function assertRedirectToSignedRoute($name = null, $parameters = []) + public function assertRedirectToSignedRoute($name = null, $parameters = [], $absolute = true) { if (! is_null($name)) { $uri = route($name, $parameters); @@ -252,7 +253,7 @@ public function assertRedirectToSignedRoute($name = null, $parameters = []) $request = Request::create($this->headers->get('Location')); PHPUnit::withResponse($this)->assertTrue( - $request->hasValidSignature(), 'The response is not a redirect to a signed route.' + $request->hasValidSignature($absolute), 'The response is not a redirect to a signed route.' ); if (! is_null($name)) { From 2f83567c1c2e650ee18a712d677f693bd7fc5fef Mon Sep 17 00:00:00 2001 From: Caleb White Date: Tue, 10 Sep 2024 08:38:03 -0500 Subject: [PATCH 040/100] fix: Builder::with closure types (#52729) --- src/Illuminate/Database/Eloquent/Builder.php | 6 +++--- types/Database/Eloquent/Builder.php | 6 ++++++ types/Database/Eloquent/Collection.php | 22 ++++++++++---------- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 5a4f4e117aa6..ff31cd8e4ae3 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -1537,8 +1537,8 @@ protected function createNestedWhere($whereSlice, $boolean = 'and') /** * Set the relationships that should be eager loaded. * - * @param string|array $relations - * @param string|\Closure|null $callback + * @param array): mixed)|string>|string $relations + * @param (\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string|null $callback * @return $this */ public function with($relations, $callback = null) @@ -1572,7 +1572,7 @@ public function without($relations) /** * Set the relationships that should be eager loaded while removing any previously added eager loading specifications. * - * @param mixed $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function withOnly($relations) diff --git a/types/Database/Eloquent/Builder.php b/types/Database/Eloquent/Builder.php index 9f59492b8402..a31d0dcacdcf 100644 --- a/types/Database/Eloquent/Builder.php +++ b/types/Database/Eloquent/Builder.php @@ -24,8 +24,14 @@ function test( assertType('Illuminate\Database\Eloquent\Builder', $query->orWhere('name', 'John')); assertType('Illuminate\Database\Eloquent\Builder', $query->whereNot('status', 'active')); assertType('Illuminate\Database\Eloquent\Builder', $query->with('relation')); + assertType('Illuminate\Database\Eloquent\Builder', $query->with(['relation' => function ($query) { + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); + }])); assertType('Illuminate\Database\Eloquent\Builder', $query->without('relation')); assertType('Illuminate\Database\Eloquent\Builder', $query->withOnly(['relation'])); + assertType('Illuminate\Database\Eloquent\Builder', $query->withOnly(['relation' => function ($query) { + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); + }])); assertType('array', $query->getModels()); assertType('array', $query->eagerLoadRelations([])); assertType('Illuminate\Database\Eloquent\Collection', $query->get()); diff --git a/types/Database/Eloquent/Collection.php b/types/Database/Eloquent/Collection.php index 4edcce5db50f..1d74831ce6f5 100644 --- a/types/Database/Eloquent/Collection.php +++ b/types/Database/Eloquent/Collection.php @@ -11,66 +11,66 @@ assertType('Illuminate\Database\Eloquent\Collection', $collection->load('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->load(['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->load(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string'], 'string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount(['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg(['string'], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists(['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing(['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing(['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorph('string', ['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorph('string', ['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorphCount('string', ['string'])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorphCount('string', ['string' => function ($query) { - // assertType('Illuminate\Database\Eloquent\Builder', $query); + // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('bool', $collection->contains(function ($user) { From c44aa73c338cdcc19808a5d90672e31700e3becd Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 11 Sep 2024 15:06:38 -0500 Subject: [PATCH 041/100] Laracon 2024 (#52710) - deferred functions - concurrency - local private files - cache::flexible - log() function --- composer.json | 4 +- config/concurrency.php | 20 +++ src/Illuminate/Cache/Repository.php | 55 ++++++ src/Illuminate/Concurrency/.gitattributes | 2 + .../Concurrency/ConcurrencyManager.php | 100 +++++++++++ .../ConcurrencyServiceProvider.php | 33 ++++ .../InvokeSerializedClosureCommand.php | 63 +++++++ src/Illuminate/Concurrency/ForkDriver.php | 28 ++++ src/Illuminate/Concurrency/LICENSE.md | 21 +++ src/Illuminate/Concurrency/ProcessDriver.php | 64 +++++++ src/Illuminate/Concurrency/SyncDriver.php | 28 ++++ src/Illuminate/Concurrency/composer.json | 35 ++++ .../Contracts/Concurrency/Driver.php | 18 ++ .../Filesystem/FilesystemAdapter.php | 36 ++++ .../Filesystem/FilesystemManager.php | 22 ++- .../Filesystem/FilesystemServiceProvider.php | 56 ++++++- .../Filesystem/LocalFilesystemAdapter.php | 104 ++++++++++++ src/Illuminate/Filesystem/ServeFile.php | 59 +++++++ .../Configuration/ApplicationBuilder.php | 11 ++ .../Foundation/Configuration/Middleware.php | 1 + .../Foundation/Defer/DeferredCallback.php | 55 ++++++ .../Defer/DeferredCallbackCollection.php | 157 ++++++++++++++++++ .../Middleware/InvokeDeferredCallbacks.php | 38 +++++ .../Providers/ArtisanServiceProvider.php | 2 + .../Providers/FoundationServiceProvider.php | 24 +++ src/Illuminate/Foundation/helpers.php | 24 +++ src/Illuminate/Log/functions.php | 17 ++ src/Illuminate/Queue/Events/JobAttempted.php | 52 ++++++ src/Illuminate/Queue/Worker.php | 7 + src/Illuminate/Support/DefaultProviders.php | 1 + .../Support/Facades/Concurrency.php | 21 +++ src/Illuminate/Support/Facades/Facade.php | 1 + .../Support/MultipleInstanceManager.php | 13 ++ src/Illuminate/Support/Sleep.php | 77 ++++++++- tests/Integration/Cache/RepositoryTest.php | 149 +++++++++++++++++ .../Concurrency/ConcurrencyTest.php | 39 +++++ .../Integration/Filesystem/ServeFileTest.php | 58 +++++++ tests/Support/SleepTest.php | 22 +++ 38 files changed, 1500 insertions(+), 17 deletions(-) create mode 100644 config/concurrency.php create mode 100644 src/Illuminate/Concurrency/.gitattributes create mode 100644 src/Illuminate/Concurrency/ConcurrencyManager.php create mode 100644 src/Illuminate/Concurrency/ConcurrencyServiceProvider.php create mode 100644 src/Illuminate/Concurrency/Console/InvokeSerializedClosureCommand.php create mode 100644 src/Illuminate/Concurrency/ForkDriver.php create mode 100644 src/Illuminate/Concurrency/LICENSE.md create mode 100644 src/Illuminate/Concurrency/ProcessDriver.php create mode 100644 src/Illuminate/Concurrency/SyncDriver.php create mode 100644 src/Illuminate/Concurrency/composer.json create mode 100644 src/Illuminate/Contracts/Concurrency/Driver.php create mode 100644 src/Illuminate/Filesystem/LocalFilesystemAdapter.php create mode 100644 src/Illuminate/Filesystem/ServeFile.php create mode 100644 src/Illuminate/Foundation/Defer/DeferredCallback.php create mode 100644 src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php create mode 100644 src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php create mode 100644 src/Illuminate/Log/functions.php create mode 100644 src/Illuminate/Queue/Events/JobAttempted.php create mode 100644 src/Illuminate/Support/Facades/Concurrency.php create mode 100644 tests/Integration/Cache/RepositoryTest.php create mode 100644 tests/Integration/Concurrency/ConcurrencyTest.php create mode 100644 tests/Integration/Filesystem/ServeFileTest.php diff --git a/composer.json b/composer.json index 3dbaa9700751..8a78ce9b10cf 100644 --- a/composer.json +++ b/composer.json @@ -64,6 +64,7 @@ "illuminate/bus": "self.version", "illuminate/cache": "self.version", "illuminate/collections": "self.version", + "illuminate/concurrency": "self.version", "illuminate/conditionable": "self.version", "illuminate/config": "self.version", "illuminate/console": "self.version", @@ -106,7 +107,7 @@ "league/flysystem-sftp-v3": "^3.0", "mockery/mockery": "^1.6", "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.1.5", + "orchestra/testbench-core": "^9.4.0", "pda/pheanstalk": "^5.0", "phpstan/phpstan": "^1.11.5", "phpunit/phpunit": "^10.5|^11.0", @@ -131,6 +132,7 @@ "src/Illuminate/Events/functions.php", "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", + "src/Illuminate/Log/functions.php", "src/Illuminate/Support/helpers.php" ], "psr-4": { diff --git a/config/concurrency.php b/config/concurrency.php new file mode 100644 index 000000000000..1d66b701f823 --- /dev/null +++ b/config/concurrency.php @@ -0,0 +1,20 @@ + env('CONCURRENCY_DRIVER', 'process'), + +]; diff --git a/src/Illuminate/Cache/Repository.php b/src/Illuminate/Cache/Repository.php index af09dd6cdbc0..77fbb52a2e5f 100755 --- a/src/Illuminate/Cache/Repository.php +++ b/src/Illuminate/Cache/Repository.php @@ -471,6 +471,61 @@ public function rememberForever($key, Closure $callback) return $value; } + /** + * Retrieve an item from the cache by key, refreshing it in the background if it is stale. + * + * @template TCacheValue + * + * @param string $key + * @param array{ 0: int, 1: int } $ttl + * @param (callable(): TCacheValue) $callback + * @param array{ seconds?: int, owner?: string }|null $lock + * @return TCacheValue + */ + public function flexible($key, $ttl, $callback, $lock = null) + { + [ + $key => $value, + "{$key}:created" => $created, + ] = $this->many([$key, "{$key}:created"]); + + if ($created === null) { + return tap(value($callback), fn ($value) => $this->putMany([ + $key => $value, + "{$key}:created" => Carbon::now()->getTimestamp(), + ], $ttl[1])); + } + + if (($created + $this->getSeconds($ttl[0])) > Carbon::now()->getTimestamp()) { + return $value; + } + + $refresh = function () use ($key, $ttl, $callback, $lock, $created) { + $this->store->lock( + "illuminate:cache:refresh:lock:{$key}", + $lock['seconds'] ?? 0, + $lock['owner'] ?? null, + )->get(function () use ($key, $callback, $created, $ttl) { + if ($created !== $this->get("{$key}:created")) { + return; + } + + $this->putMany([ + $key => value($callback), + "{$key}:created" => Carbon::now()->getTimestamp(), + ], $ttl[1]); + }); + }; + + if (function_exists('defer')) { + defer($refresh, "illuminate:cache:refresh:{$key}"); + } else { + $refresh(); + } + + return $value; + } + /** * Remove an item from the cache. * diff --git a/src/Illuminate/Concurrency/.gitattributes b/src/Illuminate/Concurrency/.gitattributes new file mode 100644 index 000000000000..7e54581c2a32 --- /dev/null +++ b/src/Illuminate/Concurrency/.gitattributes @@ -0,0 +1,2 @@ +/.github export-ignore +.gitattributes export-ignore diff --git a/src/Illuminate/Concurrency/ConcurrencyManager.php b/src/Illuminate/Concurrency/ConcurrencyManager.php new file mode 100644 index 000000000000..27646401bd21 --- /dev/null +++ b/src/Illuminate/Concurrency/ConcurrencyManager.php @@ -0,0 +1,100 @@ +instance($name); + } + + /** + * Create an instance of the process concurrency driver. + * + * @param array $config + * @return \Illuminate\Concurrency\ProcessDriver + */ + public function createProcessDriver(array $config) + { + return new ProcessDriver($this->app->make(ProcessFactory::class)); + } + + /** + * Create an instance of the fork concurrency driver. + * + * @param array $config + * @return \Illuminate\Concurrency\ForkDriver + */ + public function createForkDriver(array $config) + { + if (! $this->app->runningInConsole()) { + throw new RuntimeException('Due to PHP limitations, the fork driver may not be used within web requests.'); + } + + if (! class_exists(Fork::class)) { + throw new RuntimeException('Please install the "spatie/fork" Composer package in order to utilize the "fork" driver.'); + } + + return new ForkDriver; + } + + /** + * Create an instance of the sync concurrency driver. + * + * @param array $config + * @return \Illuminate\Concurrency\SyncDriver + */ + public function createSyncDriver(array $config) + { + return new SyncDriver; + } + + /** + * Get the default instance name. + * + * @return string + */ + public function getDefaultInstance() + { + return $this->app['config']['concurrency.default'] ?? 'process'; + } + + /** + * Set the default instance name. + * + * @param string $name + * @return void + */ + public function setDefaultInstance($name) + { + $this->app['config']['concurrency.default'] = $name; + } + + /** + * Get the instance specific configuration. + * + * @param string $name + * @return array + */ + public function getInstanceConfig($name) + { + return $this->app['config']->get( + 'concurrency.drivers.'.$name, ['driver' => $name], + ); + } +} diff --git a/src/Illuminate/Concurrency/ConcurrencyServiceProvider.php b/src/Illuminate/Concurrency/ConcurrencyServiceProvider.php new file mode 100644 index 000000000000..2afd9b6312e0 --- /dev/null +++ b/src/Illuminate/Concurrency/ConcurrencyServiceProvider.php @@ -0,0 +1,33 @@ +app->singleton(ConcurrencyManager::class, function ($app) { + return new ConcurrencyManager($app); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return [ + ConcurrencyManager::class, + ]; + } +} diff --git a/src/Illuminate/Concurrency/Console/InvokeSerializedClosureCommand.php b/src/Illuminate/Concurrency/Console/InvokeSerializedClosureCommand.php new file mode 100644 index 000000000000..faaa4d29c279 --- /dev/null +++ b/src/Illuminate/Concurrency/Console/InvokeSerializedClosureCommand.php @@ -0,0 +1,63 @@ +output->write(json_encode([ + 'successful' => true, + 'result' => serialize($this->laravel->call(match (true) { + ! is_null($this->argument('code')) => unserialize($this->argument('code')), + isset($_SERVER['LARAVEL_INVOKABLE_CLOSURE']) => unserialize($_SERVER['LARAVEL_INVOKABLE_CLOSURE']), + default => fn () => null, + })), + ])); + } catch (Throwable $e) { + report($e); + + $this->output->write(json_encode([ + 'successful' => false, + 'exception' => get_class($e), + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + ])); + } + } +} diff --git a/src/Illuminate/Concurrency/ForkDriver.php b/src/Illuminate/Concurrency/ForkDriver.php new file mode 100644 index 000000000000..beca07d7e44b --- /dev/null +++ b/src/Illuminate/Concurrency/ForkDriver.php @@ -0,0 +1,28 @@ +run(...Arr::wrap($tasks)); + } + + /** + * Start the given tasks in the background after the current task has finished. + */ + public function defer(Closure|array $tasks): DeferredCallback + { + return defer(fn () => $this->run($tasks)); + } +} diff --git a/src/Illuminate/Concurrency/LICENSE.md b/src/Illuminate/Concurrency/LICENSE.md new file mode 100644 index 000000000000..79810c848f8b --- /dev/null +++ b/src/Illuminate/Concurrency/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Illuminate/Concurrency/ProcessDriver.php b/src/Illuminate/Concurrency/ProcessDriver.php new file mode 100644 index 000000000000..0cd0add93ff1 --- /dev/null +++ b/src/Illuminate/Concurrency/ProcessDriver.php @@ -0,0 +1,64 @@ +find(false); + + $results = $this->processFactory->pool(function (Pool $pool) use ($tasks, $php) { + foreach (Arr::wrap($tasks) as $task) { + $pool->path(base_path())->env([ + 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), + ])->command($php.' artisan invoke-serialized-closure'); + } + })->start()->wait(); + + return $results->collect()->map(function ($result) { + $result = json_decode($result->output(), true); + + if (! $result['successful']) { + throw new $result['exception']( + $result['message'] + ); + } + + return unserialize($result['result']); + })->all(); + } + + /** + * Start the given tasks in the background after the current task has finished. + */ + public function defer(Closure|array $tasks): DeferredCallback + { + return defer(function () use ($tasks) { + foreach (Arr::wrap($tasks) as $task) { + $this->processFactory->path(base_path())->env([ + 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), + ])->run('php artisan invoke-serialized-closure 2>&1 &'); + } + }); + } +} diff --git a/src/Illuminate/Concurrency/SyncDriver.php b/src/Illuminate/Concurrency/SyncDriver.php new file mode 100644 index 000000000000..10580124f3bd --- /dev/null +++ b/src/Illuminate/Concurrency/SyncDriver.php @@ -0,0 +1,28 @@ +map( + fn ($task) => $task() + )->all(); + } + + /** + * Start the given tasks in the background after the current task has finished. + */ + public function defer(Closure|array $tasks): DeferredCallback + { + return defer(fn () => collect(Arr::wrap($tasks))->each(fn ($task) => $task())); + } +} diff --git a/src/Illuminate/Concurrency/composer.json b/src/Illuminate/Concurrency/composer.json new file mode 100644 index 000000000000..6476f73aafde --- /dev/null +++ b/src/Illuminate/Concurrency/composer.json @@ -0,0 +1,35 @@ +{ + "name": "illuminate/concurrency", + "description": "The Illuminate Concurrency package.", + "license": "MIT", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "require": { + "php": "^8.2", + "illuminate/process": "^11.0", + "laravel/serializable-closure": "^1.2.2" + }, + "autoload": { + "psr-4": { + "Illuminate\\Support\\": "" + } + }, + "extra": { + "branch-alias": { + "dev-master": "11.x-dev" + } + }, + "config": { + "sort-packages": true + }, + "minimum-stability": "dev" +} diff --git a/src/Illuminate/Contracts/Concurrency/Driver.php b/src/Illuminate/Contracts/Concurrency/Driver.php new file mode 100644 index 000000000000..ce8f2ae0d900 --- /dev/null +++ b/src/Illuminate/Contracts/Concurrency/Driver.php @@ -0,0 +1,18 @@ +serveCallback) + ? call_user_func($this->serveCallback, $request, $path, $headers) + : $this->response($path, $name, $headers); + } + + /** + * Create a streamed download response for a given file. + * + * @param string $path + * @param string|null $name + * @param array $headers * @return \Symfony\Component\HttpFoundation\StreamedResponse */ public function download($path, $name = null, array $headers = []) @@ -948,6 +973,17 @@ protected function parseVisibility($visibility) }; } + /** + * Define a custom callback that generates file download responses. + * + * @param \Closure $callback + * @return void + */ + public function serveUsing(Closure $callback) + { + $this->serveCallback = $callback; + } + /** * Define a custom temporary URL builder callback. * diff --git a/src/Illuminate/Filesystem/FilesystemManager.php b/src/Illuminate/Filesystem/FilesystemManager.php index 20ba1dd054f9..0fa06eaf6d1f 100644 --- a/src/Illuminate/Filesystem/FilesystemManager.php +++ b/src/Illuminate/Filesystem/FilesystemManager.php @@ -137,19 +137,19 @@ protected function resolve($name, $config = null) throw new InvalidArgumentException("Disk [{$name}] does not have a configured driver."); } - $name = $config['driver']; + $driver = $config['driver']; - if (isset($this->customCreators[$name])) { + if (isset($this->customCreators[$driver])) { return $this->callCustomCreator($config); } - $driverMethod = 'create'.ucfirst($name).'Driver'; + $driverMethod = 'create'.ucfirst($driver).'Driver'; if (! method_exists($this, $driverMethod)) { - throw new InvalidArgumentException("Driver [{$name}] is not supported."); + throw new InvalidArgumentException("Driver [{$driver}] is not supported."); } - return $this->{$driverMethod}($config); + return $this->{$driverMethod}($config, $name); } /** @@ -167,9 +167,10 @@ protected function callCustomCreator(array $config) * Create an instance of the local driver. * * @param array $config + * @param string $name * @return \Illuminate\Contracts\Filesystem\Filesystem */ - public function createLocalDriver(array $config) + public function createLocalDriver(array $config, string $name) { $visibility = PortableVisibilityConverter::fromArray( $config['permissions'] ?? [], @@ -184,7 +185,14 @@ public function createLocalDriver(array $config) $config['root'], $visibility, $config['lock'] ?? LOCK_EX, $links ); - return new FilesystemAdapter($this->createFlysystem($adapter, $config), $adapter, $config); + return (new LocalFilesystemAdapter( + $this->createFlysystem($adapter, $config), $adapter, $config + ))->diskName( + $name + )->shouldServeSignedUrls( + $config['serve'] ?? false, + fn () => $this->app['url'], + ); } /** diff --git a/src/Illuminate/Filesystem/FilesystemServiceProvider.php b/src/Illuminate/Filesystem/FilesystemServiceProvider.php index ff348a224921..a6ad8e1a7b34 100644 --- a/src/Illuminate/Filesystem/FilesystemServiceProvider.php +++ b/src/Illuminate/Filesystem/FilesystemServiceProvider.php @@ -2,10 +2,22 @@ namespace Illuminate\Filesystem; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; class FilesystemServiceProvider extends ServiceProvider { + /** + * Bootstrap the filesystem. + * + * @return void + */ + public function boot() + { + $this->serveFiles(); + } + /** * Register the service provider. * @@ -14,7 +26,6 @@ class FilesystemServiceProvider extends ServiceProvider public function register() { $this->registerNativeFilesystem(); - $this->registerFlysystem(); } @@ -60,6 +71,49 @@ protected function registerManager() }); } + /** + * Register protected file serving. + * + * @return void + */ + protected function serveFiles() + { + if ($this->app->routesAreCached()) { + return; + } + + foreach ($this->app['config']['filesystems.disks'] ?? [] as $disk => $config) { + if (! $this->shouldServeFiles($config)) { + continue; + } + + $this->app->booted(function () use ($disk, $config) { + $uri = isset($config['url']) + ? rtrim(parse_url($config['url'])['path'], '/') + : '/storage'; + + Route::get($uri.'/{path}', function (Request $request, string $path) use ($disk, $config) { + return (new ServeFile( + $disk, + $config, + $this->app->isProduction() + ))($request, $path); + })->where('path', '.*')->name('storage.'.$disk); + }); + } + } + + /** + * Determine if the disk is serveable. + * + * @param array $config + * @return bool + */ + protected function shouldServeFiles(array $config) + { + return $config['driver'] === 'local' && ($config['serve'] ?? false); + } + /** * Get the default file driver. * diff --git a/src/Illuminate/Filesystem/LocalFilesystemAdapter.php b/src/Illuminate/Filesystem/LocalFilesystemAdapter.php new file mode 100644 index 000000000000..2c85ce1081e0 --- /dev/null +++ b/src/Illuminate/Filesystem/LocalFilesystemAdapter.php @@ -0,0 +1,104 @@ +temporaryUrlCallback || ( + $this->shouldServeSignedUrls && $this->urlGeneratorResolver instanceof Closure + ); + } + + /** + * Get a temporary URL for the file at the given path. + * + * @param string $path + * @param \DateTimeInterface $expiration + * @param array $options + * @return string + */ + public function temporaryUrl($path, $expiration, array $options = []) + { + if ($this->temporaryUrlCallback) { + return $this->temporaryUrlCallback->bindTo($this, static::class)( + $path, $expiration, $options + ); + } + + if (! $this->providesTemporaryUrls()) { + throw new RuntimeException('This driver does not support creating temporary URLs.'); + } + + $url = call_user_func($this->urlGeneratorResolver); + + return $url->to($url->temporarySignedRoute( + 'storage.'.$this->disk, + $expiration, + ['path' => $path], + absolute: false + )); + } + + /** + * Specify the name of the disk the adapter is managing. + * + * @param string $disk + * @return $this + */ + public function diskName(string $disk) + { + $this->disk = $disk; + + return $this; + } + + /** + * Indiate that signed URLs should serve the corresponding files. + * + * @param bool $serve + * @param \Closure|null $urlGeneratorResolver + * @return $this + */ + public function shouldServeSignedUrls(bool $serve = true, ?Closure $urlGeneratorResolver = null) + { + $this->shouldServeSignedUrls = $serve; + $this->urlGeneratorResolver = $urlGeneratorResolver; + + return $this; + } +} diff --git a/src/Illuminate/Filesystem/ServeFile.php b/src/Illuminate/Filesystem/ServeFile.php new file mode 100644 index 000000000000..0fd577e91db2 --- /dev/null +++ b/src/Illuminate/Filesystem/ServeFile.php @@ -0,0 +1,59 @@ +hasValidSignature($request), + $this->isProduction ? 404 : 403 + ); + try { + abort_unless(Storage::disk($this->disk)->exists($path), 404); + + $headers = [ + 'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0', + 'Content-Security-Policy' => "default-src 'none'; style-src 'unsafe-inline'; sandbox", + ]; + + return tap( + Storage::disk($this->disk)->serve($request, $path, headers: $headers), + function ($response) use ($headers) { + if (! $response->headers->has('Content-Security-Policy')) { + $response->headers->replace($headers); + } + } + ); + } catch (PathTraversalDetected $e) { + abort(404); + } + } + + /** + * Determine if the request has a valid signature if applicable. + */ + protected function hasValidSignature(Request $request): bool + { + return ($this->config['visibility'] ?? 'private') === 'public' || + $request->hasValidRelativeSignature(); + } +} diff --git a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php index 6065424e1198..89ec3b9cc50f 100644 --- a/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php +++ b/src/Illuminate/Foundation/Configuration/ApplicationBuilder.php @@ -27,6 +27,13 @@ class ApplicationBuilder */ protected array $pendingProviders = []; + /** + * Any additional routing callbacks that should be invoked while registering routes. + * + * @var array + */ + protected array $additionalRoutingCallbacks = []; + /** * The Folio / page middleware that have been defined by the user. * @@ -222,6 +229,10 @@ protected function buildRoutingCallback(array|string|null $web, } } + foreach ($this->additionalRoutingCallbacks as $callback) { + $callback(); + } + if (is_string($pages) && realpath($pages) !== false && class_exists(Folio::class)) { diff --git a/src/Illuminate/Foundation/Configuration/Middleware.php b/src/Illuminate/Foundation/Configuration/Middleware.php index 30e60ed98238..52b83acf518b 100644 --- a/src/Illuminate/Foundation/Configuration/Middleware.php +++ b/src/Illuminate/Foundation/Configuration/Middleware.php @@ -408,6 +408,7 @@ public function priority(array $priority) public function getGlobalMiddleware() { $middleware = $this->global ?: array_values(array_filter([ + \Illuminate\Foundation\Http\Middleware\InvokeDeferredCallbacks::class, $this->trustHosts ? \Illuminate\Http\Middleware\TrustHosts::class : null, \Illuminate\Http\Middleware\TrustProxies::class, \Illuminate\Http\Middleware\HandleCors::class, diff --git a/src/Illuminate/Foundation/Defer/DeferredCallback.php b/src/Illuminate/Foundation/Defer/DeferredCallback.php new file mode 100644 index 000000000000..14b8a85690dd --- /dev/null +++ b/src/Illuminate/Foundation/Defer/DeferredCallback.php @@ -0,0 +1,55 @@ +name = $name ?? (string) Str::uuid(); + } + + /** + * Specify the name of the deferred callback so it can be cancelled later. + * + * @param string $name + * @return $this + */ + public function name(string $name): self + { + $this->name = $name; + + return $this; + } + + /** + * Indicate that the deferred callback should run even on unsuccessful requests and jobs. + * + * @param bool $always + * @return $this + */ + public function always(bool $always = true): self + { + $this->always = $always; + + return $this; + } + + /** + * Invoke the deferred callback. + * + * @return void + */ + public function __invoke(): void + { + call_user_func($this->callback); + } +} diff --git a/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php b/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php new file mode 100644 index 000000000000..55749481da59 --- /dev/null +++ b/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php @@ -0,0 +1,157 @@ +callbacks)[0]; + } + + /** + * Invoke the deferred callbacks. + * + * @return void + */ + public function invoke(): void + { + $this->invokeWhen(fn () => true); + } + + /** + * Invoke the deferred callbacks if the given truth test evaluates to true. + * + * @param \Closure $when + * @return void + */ + public function invokeWhen(?Closure $when = null): void + { + $when ??= fn () => true; + + $this->forgetDuplicates(); + + foreach ($this->callbacks as $index => $callback) { + if ($when($callback)) { + rescue($callback); + } + + unset($this->callbacks[$index]); + } + } + + /** + * Remove any deferred callbacks with the given name. + * + * @param string $name + * @return void + */ + public function forget(string $name): void + { + $this->callbacks = collect($this->callbacks) + ->reject(fn ($callback) => $callback->name === $name) + ->values() + ->all(); + } + + /** + * Remove any duplicate callbacks. + * + * @return $this + */ + protected function forgetDuplicates(): self + { + $this->callbacks = collect($this->callbacks) + ->reverse() + ->unique(fn ($c) => $c->name) + ->reverse() + ->values() + ->all(); + + return $this; + } + + /** + * Determine if the collection has a callback with the given key. + * + * @param mixed $offset + * @return bool + */ + public function offsetExists(mixed $offset): bool + { + $this->forgetDuplicates(); + + return isset($this->callbacks[$offset]); + } + + /** + * Get the callback with the given key. + * + * @param mixed $offset + * @return mixed + */ + public function offsetGet(mixed $offset): mixed + { + $this->forgetDuplicates(); + + return $this->callbacks[$offset]; + } + + /** + * Set teh callback with the given key. + * + * @param mixed $offset + * @param mixed $value + * @return void + */ + public function offsetSet(mixed $offset, mixed $value): void + { + if (is_null($offset)) { + $this->callbacks[] = $value; + } else { + $this->callbacks[$offset] = $value; + } + } + + /** + * Remove the callback with the given key from the collection. + * + * @param mixed $offset + * @return void + */ + public function offsetUnset(mixed $offset): void + { + $this->forgetDuplicates(); + + unset($this->callbacks[$offset]); + } + + /** + * Determine how many callbacks are in the collection. + * + * @return int + */ + public function count(): int + { + $this->forgetDuplicates(); + + return count($this->callbacks); + } +} diff --git a/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php b/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php new file mode 100644 index 000000000000..9a804332e73e --- /dev/null +++ b/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php @@ -0,0 +1,38 @@ +make(DeferredCallbackCollection::class) + ->invokeWhen(fn ($callback) => $response->getStatusCode() < 400 || $callback->always); + } +} diff --git a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php index 4ec8289653d7..6ed57c76326e 100755 --- a/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/ArtisanServiceProvider.php @@ -7,6 +7,7 @@ use Illuminate\Cache\Console\ClearCommand as CacheClearCommand; use Illuminate\Cache\Console\ForgetCommand as CacheForgetCommand; use Illuminate\Cache\Console\PruneStaleTagsCommand; +use Illuminate\Concurrency\Console\InvokeSerializedClosureCommand; use Illuminate\Console\Scheduling\ScheduleClearCacheCommand; use Illuminate\Console\Scheduling\ScheduleFinishCommand; use Illuminate\Console\Scheduling\ScheduleInterruptCommand; @@ -135,6 +136,7 @@ class ArtisanServiceProvider extends ServiceProvider implements DeferrableProvid 'EventCache' => EventCacheCommand::class, 'EventClear' => EventClearCommand::class, 'EventList' => EventListCommand::class, + 'InvokeSerializedClosure' => InvokeSerializedClosureCommand::class, 'KeyGenerate' => KeyGenerateCommand::class, 'Optimize' => OptimizeCommand::class, 'OptimizeClear' => OptimizeClearCommand::class, diff --git a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php index c3455b92105b..6711dd2d770f 100644 --- a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php @@ -2,6 +2,7 @@ namespace Illuminate\Foundation\Providers; +use Illuminate\Console\Events\CommandFinished; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Contracts\Console\Kernel as ConsoleKernel; use Illuminate\Contracts\Container\Container; @@ -12,6 +13,7 @@ use Illuminate\Database\ConnectionInterface; use Illuminate\Database\Grammar; use Illuminate\Foundation\Console\CliDumper; +use Illuminate\Foundation\Defer\DeferredCallbackCollection; use Illuminate\Foundation\Exceptions\Renderer\Listener; use Illuminate\Foundation\Exceptions\Renderer\Mappers\BladeMapper; use Illuminate\Foundation\Exceptions\Renderer\Renderer; @@ -22,6 +24,7 @@ use Illuminate\Http\Client\Factory as HttpFactory; use Illuminate\Http\Request; use Illuminate\Log\Events\MessageLogged; +use Illuminate\Queue\Events\JobAttempted; use Illuminate\Support\AggregateServiceProvider; use Illuminate\Support\Facades\URL; use Illuminate\Testing\LoggedExceptionCollection; @@ -86,6 +89,7 @@ public function register() $this->registerDumper(); $this->registerRequestValidation(); $this->registerRequestSignatureValidation(); + $this->registerDeferHandler(); $this->registerExceptionTracking(); $this->registerExceptionRenderer(); $this->registerMaintenanceModeManager(); @@ -184,6 +188,26 @@ public function registerRequestSignatureValidation() }); } + /** + * Register the "defer" function termination handler. + * + * @return void + */ + protected function registerDeferHandler() + { + $this->app->scoped(DeferredCallbackCollection::class); + + $this->app['events']->listen(function (CommandFinished $event) { + app(DeferredCallbackCollection::class)->invokeWhen(fn ($callback) => app()->runningInConsole() && ($event->exitCode === 0 || $callback->always) + ); + }); + + $this->app['events']->listen(function (JobAttempted $event) { + app(DeferredCallbackCollection::class)->invokeWhen(fn ($callback) => $event->connectionName !== 'sync' && ($event->successful() || $callback->always) + ); + }); + } + /** * Register an event listener to track logged exceptions. * diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index d73632529bff..8ce88f468033 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -14,6 +14,8 @@ use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Foundation\Bus\PendingClosureDispatch; use Illuminate\Foundation\Bus\PendingDispatch; +use Illuminate\Foundation\Defer\DeferredCallback; +use Illuminate\Foundation\Defer\DeferredCallbackCollection; use Illuminate\Foundation\Mix; use Illuminate\Http\Exceptions\HttpResponseException; use Illuminate\Log\Context\Repository as ContextRepository; @@ -399,6 +401,28 @@ function decrypt($value, $unserialize = true) } } +if (! function_exists('defer')) { + /** + * Defer execution of the given callback. + * + * @param callable|null $callback + * @param string|null $name + * @param bool $always + * @return \Illuminate\Foundation\Defer\DeferredCallback + */ + function defer(?callable $callback = null, ?string $name = null, bool $always = false) + { + if ($callback === null) { + return app(DeferredCallbackCollection::class); + } + + return tap( + new DeferredCallback($callback, $name, $always), + fn ($deferred) => app(DeferredCallbackCollection::class)[] = $deferred + ); + } +} + if (! function_exists('dispatch')) { /** * Dispatch a job to its appropriate handler. diff --git a/src/Illuminate/Log/functions.php b/src/Illuminate/Log/functions.php new file mode 100644 index 000000000000..8cbf3ef65593 --- /dev/null +++ b/src/Illuminate/Log/functions.php @@ -0,0 +1,17 @@ +job = $job; + $this->connectionName = $connectionName; + $this->exceptionOccurred = $exceptionOccurred; + } + + /** + * Determine if the job completed with failing or an unhandled exception occurring. + * + * @return bool + */ + public function successful(): bool + { + return ! $this->job->hasFailed() && ! $this->exceptionOccurred; + } +} diff --git a/src/Illuminate/Queue/Worker.php b/src/Illuminate/Queue/Worker.php index c9f335a66467..83b4e284d40c 100644 --- a/src/Illuminate/Queue/Worker.php +++ b/src/Illuminate/Queue/Worker.php @@ -7,6 +7,7 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Queue\Factory as QueueManager; use Illuminate\Database\DetectsLostConnections; +use Illuminate\Queue\Events\JobAttempted; use Illuminate\Queue\Events\JobExceptionOccurred; use Illuminate\Queue\Events\JobPopped; use Illuminate\Queue\Events\JobPopping; @@ -440,7 +441,13 @@ public function process($connectionName, $job, WorkerOptions $options) $this->raiseAfterJobEvent($connectionName, $job); } catch (Throwable $e) { + $exceptionOccurred = true; + $this->handleJobException($connectionName, $job, $options, $e); + } finally { + $this->events->dispatch(new JobAttempted( + $connectionName, $job, $exceptionOccurred ?? false + )); } } diff --git a/src/Illuminate/Support/DefaultProviders.php b/src/Illuminate/Support/DefaultProviders.php index 57598a614ec0..a89f47bb0d28 100644 --- a/src/Illuminate/Support/DefaultProviders.php +++ b/src/Illuminate/Support/DefaultProviders.php @@ -24,6 +24,7 @@ public function __construct(?array $providers = null) \Illuminate\Bus\BusServiceProvider::class, \Illuminate\Cache\CacheServiceProvider::class, \Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, + \Illuminate\Concurrency\ConcurrencyServiceProvider::class, \Illuminate\Cookie\CookieServiceProvider::class, \Illuminate\Database\DatabaseServiceProvider::class, \Illuminate\Encryption\EncryptionServiceProvider::class, diff --git a/src/Illuminate/Support/Facades/Concurrency.php b/src/Illuminate/Support/Facades/Concurrency.php new file mode 100644 index 000000000000..4a9eab3a0d8d --- /dev/null +++ b/src/Illuminate/Support/Facades/Concurrency.php @@ -0,0 +1,21 @@ + Broadcast::class, 'Bus' => Bus::class, 'Cache' => Cache::class, + 'Concurrency' => Concurrency::class, 'Config' => Config::class, 'Context' => Context::class, 'Cookie' => Cookie::class, diff --git a/src/Illuminate/Support/MultipleInstanceManager.php b/src/Illuminate/Support/MultipleInstanceManager.php index faf0123614ec..05a8c23b4135 100644 --- a/src/Illuminate/Support/MultipleInstanceManager.php +++ b/src/Illuminate/Support/MultipleInstanceManager.php @@ -201,6 +201,19 @@ public function extend($name, Closure $callback) return $this; } + /** + * Set the application instance used by the manager. + * + * @param \Illuminate\Contracts\Foundation\Application $app + * @return $this + */ + public function setApplication($app) + { + $this->app = $app; + + return $this; + } + /** * Dynamically call the default instance. * diff --git a/src/Illuminate/Support/Sleep.php b/src/Illuminate/Support/Sleep.php index ebb8f4215b03..f65141e12f77 100644 --- a/src/Illuminate/Support/Sleep.php +++ b/src/Illuminate/Support/Sleep.php @@ -3,6 +3,7 @@ namespace Illuminate\Support; use Carbon\CarbonInterval; +use Closure; use DateInterval; use Illuminate\Support\Traits\Macroable; use PHPUnit\Framework\Assert as PHPUnit; @@ -33,6 +34,13 @@ class Sleep */ public $duration; + /** + * The callback that determines if sleeping should continue. + * + * @var \Closure + */ + public $while; + /** * The pending duration to sleep. * @@ -61,6 +69,13 @@ class Sleep */ protected $shouldSleep = true; + /** + * Indicates if the instance already slept via `then()`. + * + * @var bool + */ + protected $alreadySlept = false; + /** * Create a new class instance. * @@ -247,6 +262,34 @@ public function and($duration) return $this; } + /** + * Sleep while a given callback returns "true". + * + * @param \Closure $callback + * @return $this + */ + public function while(Closure $callback) + { + $this->while = $callback; + + return $this; + } + + /** + * Specify a callback that should be executed after sleeping. + * + * @param callable $then + * @return mixed + */ + public function then(callable $then) + { + $this->goodnight(); + + $this->alreadySlept = true; + + return $then(); + } + /** * Handle the object's destruction. * @@ -254,7 +297,17 @@ public function and($duration) */ public function __destruct() { - if (! $this->shouldSleep) { + $this->goodnight(); + } + + /** + * Handle the object's destruction. + * + * @return void + */ + protected function goodnight() + { + if ($this->alreadySlept || ! $this->shouldSleep) { return; } @@ -280,16 +333,24 @@ public function __destruct() $seconds = (int) $remaining->totalSeconds; - if ($seconds > 0) { - sleep($seconds); + $while = $this->while ?: function () { + static $return = [true, false]; - $remaining = $remaining->subSeconds($seconds); - } + return array_shift($return); + }; - $microseconds = (int) $remaining->totalMicroseconds; + while ($while()) { + if ($seconds > 0) { + sleep($seconds); - if ($microseconds > 0) { - usleep($microseconds); + $remaining = $remaining->subSeconds($seconds); + } + + $microseconds = (int) $remaining->totalMicroseconds; + + if ($microseconds > 0) { + usleep($microseconds); + } } } diff --git a/tests/Integration/Cache/RepositoryTest.php b/tests/Integration/Cache/RepositoryTest.php new file mode 100644 index 000000000000..77fd8c95302c --- /dev/null +++ b/tests/Integration/Cache/RepositoryTest.php @@ -0,0 +1,149 @@ +flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + + $this->assertSame(1, $value); + $this->assertCount(0, defer()); + $this->assertSame(1, $cache->get('foo')); + $this->assertSame(946684800, $cache->get('foo:created')); + + // Cache is fresh. The value should be retrieved from the cache and used... + $value = $cache->flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + $this->assertSame(1, $value); + $this->assertCount(0, defer()); + $this->assertSame(1, $cache->get('foo')); + $this->assertSame(946684800, $cache->get('foo:created')); + + Carbon::setTestNow(now()->addSeconds(11)); + + // Cache is now "stale". The stored value should be used and a deferred + // callback should be registered to refresh the cache. + $value = $cache->flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + $this->assertSame(1, $value); + $this->assertCount(1, defer()); + $this->assertSame(1, $cache->get('foo')); + $this->assertSame(946684800, $cache->get('foo:created')); + + // We will hit it again within the same request. This should not queue + // up an additional deferred callback as only one can be registered at + // a time for each key. + $value = $cache->flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + $this->assertSame(1, $value); + $this->assertCount(1, defer()); + $this->assertSame(1, $cache->get('foo')); + $this->assertSame(946684800, $cache->get('foo:created')); + + // We will now simulate the end of the request lifecycle by executing the + // deferred callback. This should refresh the cache. + defer()->invoke(); + $this->assertCount(0, defer()); + $this->assertSame(2, $cache->get('foo')); // this has been updated! + $this->assertSame(946684811, $cache->get('foo:created')); // this has been updated! + + // Now the cache is fresh again... + $value = $cache->flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + $this->assertSame(2, $value); + $this->assertCount(0, defer()); + $this->assertSame(2, $cache->get('foo')); + $this->assertSame(946684811, $cache->get('foo:created')); + + // Let's now progress time beyond the stale TTL... + Carbon::setTestNow(now()->addSeconds(21)); + + // Now the values should have left the cache. We should refresh. + $value = $cache->flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + $this->assertSame(3, $value); + $this->assertCount(0, defer()); + $this->assertSame(3, $cache->get('foo')); + $this->assertSame(946684832, $cache->get('foo:created')); + + // Now lets see what happens when another request, job, or command is + // also trying to refresh the same key at the same time. Will push past + // the "fresh" TTL and register a deferred callback. + Carbon::setTestNow(now()->addSeconds(11)); + $value = $cache->flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + $this->assertSame(3, $value); + $this->assertCount(1, defer()); + $this->assertSame(3, $cache->get('foo')); + $this->assertSame(946684832, $cache->get('foo:created')); + + // Now we will execute the deferred callback but we will first aquire + // our own lock. This means that the value should not be refreshed by + // deferred callback. + /** @var Lock */ + $lock = $cache->lock('illuminate:cache:refresh:lock:foo'); + + $this->assertTrue($lock->acquire()); + defer()->first()(); + $this->assertSame(3, $value); + $this->assertCount(1, defer()); + $this->assertSame(3, $cache->get('foo')); + $this->assertSame(946684832, $cache->get('foo:created')); + $this->assertTrue($lock->release()); + + // Now we have cleared the lock we will, one last time, confirm that + // the deferred callack does refresh the value when the lock is not active. + defer()->invoke(); + $this->assertCount(0, defer()); + $this->assertSame(4, $cache->get('foo')); + $this->assertSame(946684843, $cache->get('foo:created')); + + // The last thing is to check that we don't refresh the cache in the + // deferred callback if another thread has already done the work for us. + // We will make the cache stale... + Carbon::setTestNow(now()->addSeconds(11)); + $value = $cache->flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + $this->assertSame(4, $value); + $this->assertCount(1, defer()); + $this->assertSame(4, $cache->get('foo')); + $this->assertSame(946684843, $cache->get('foo:created')); + + // There is now a deferred callback ready to refresh the cache. We will + // simulate another thread updating the value. + $cache->putMany([ + 'foo' => 99, + 'foo:created' => 946684863, + ]); + + // then we will run the refresh callback + defer()->invoke(); + $value = $cache->flexible('foo', [10, 20], function () use (&$count) { + return ++$count; + }); + $this->assertSame(99, $value); + $this->assertCount(0, defer()); + $this->assertSame(99, $cache->get('foo')); + $this->assertSame(946684863, $cache->get('foo:created')); + } +} diff --git a/tests/Integration/Concurrency/ConcurrencyTest.php b/tests/Integration/Concurrency/ConcurrencyTest.php new file mode 100644 index 000000000000..76012712c375 --- /dev/null +++ b/tests/Integration/Concurrency/ConcurrencyTest.php @@ -0,0 +1,39 @@ +defineCacheRoutes(<< 1 + 1, + fn () => 2 + 2, + ]); +}); +PHP); + + parent::setUp(); + } + + public function testWorkCanBeDistributed() + { + $response = $this->get('concurrency') + ->assertOk(); + + [$first, $second] = $response->original; + + $this->assertEquals(2, $first); + $this->assertEquals(4, $second); + } +} diff --git a/tests/Integration/Filesystem/ServeFileTest.php b/tests/Integration/Filesystem/ServeFileTest.php new file mode 100644 index 000000000000..c5219761ab10 --- /dev/null +++ b/tests/Integration/Filesystem/ServeFileTest.php @@ -0,0 +1,58 @@ +afterApplicationCreated(function () { + Storage::put('serve-file-test.txt', 'Hello World'); + }); + + $this->beforeApplicationDestroyed(function () { + Storage::delete('serve-file-test.txt'); + }); + + parent::setUp(); + } + + public function testItCanServeAnExistingFile() + { + $url = Storage::temporaryUrl('serve-file-test.txt', now()->addMinutes(1)); + + $response = $this->get($url); + + $this->assertEquals('Hello World', $response->streamedContent()); + } + + public function testItWill404OnMissingFile() + { + $url = Storage::temporaryUrl('serve-missing-test.txt', now()->addMinutes(1)); + + $response = $this->get($url); + + $response->assertNotFound(); + } + + public function testItWill403OnWrongSignature() + { + $url = Storage::temporaryUrl('serve-file-test.txt', now()->addMinutes(1)); + + $url = $url.'c'; + + $response = $this->get($url); + + $response->assertForbidden(); + } + + protected function getEnvironmentSetup($app) + { + tap($app['config'], function ($config) { + $config->set('filesystems.disks.local.serve', true); + }); + } +} diff --git a/tests/Support/SleepTest.php b/tests/Support/SleepTest.php index bf6b9123ce50..809ba41b8629 100644 --- a/tests/Support/SleepTest.php +++ b/tests/Support/SleepTest.php @@ -32,6 +32,28 @@ public function testItSleepsForSeconds() $this->assertEqualsWithDelta(1, $end - $start, 0.03); } + public function testCallbacksMayBeExecutedUsingThen() + { + $this->assertEquals(123, Sleep::for(1)->milliseconds()->then(fn () => 123)); + } + + public function testSleepRespectsWhile() + { + $_SERVER['__sleep.while'] = 0; + + $result = Sleep::for(10)->milliseconds()->while(function () { + static $results = [true, true, false]; + $_SERVER['__sleep.while']++; + + return array_shift($results); + })->then(fn () => 100); + + $this->assertEquals(3, $_SERVER['__sleep.while']); + $this->assertEquals(100, $result); + + unset($_SERVER['__sleep.while']); + } + public function testItSleepsForSecondsWithMilliseconds() { $start = microtime(true); From aaa46acf36a9aff1ea94248160cd2a6fb921d51b Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 11 Sep 2024 20:07:05 +0000 Subject: [PATCH 042/100] Update facade docblocks --- src/Illuminate/Support/Facades/Cache.php | 1 + src/Illuminate/Support/Facades/Storage.php | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Cache.php b/src/Illuminate/Support/Facades/Cache.php index 729f88488797..e5e802764d4c 100755 --- a/src/Illuminate/Support/Facades/Cache.php +++ b/src/Illuminate/Support/Facades/Cache.php @@ -31,6 +31,7 @@ * @method static mixed remember(string $key, \Closure|\DateTimeInterface|\DateInterval|int|null $ttl, \Closure $callback) * @method static mixed sear(string $key, \Closure $callback) * @method static mixed rememberForever(string $key, \Closure $callback) + * @method static mixed flexible(string $key, void $ttl, callable $callback, void $lock = null) * @method static bool forget(string $key) * @method static bool delete(string $key) * @method static bool deleteMultiple(iterable $keys) diff --git a/src/Illuminate/Support/Facades/Storage.php b/src/Illuminate/Support/Facades/Storage.php index b6391eaf14a0..f2dc0d77912b 100644 --- a/src/Illuminate/Support/Facades/Storage.php +++ b/src/Illuminate/Support/Facades/Storage.php @@ -9,7 +9,7 @@ * @method static \Illuminate\Contracts\Filesystem\Filesystem disk(string|null $name = null) * @method static \Illuminate\Contracts\Filesystem\Cloud cloud() * @method static \Illuminate\Contracts\Filesystem\Filesystem build(string|array $config) - * @method static \Illuminate\Contracts\Filesystem\Filesystem createLocalDriver(array $config) + * @method static \Illuminate\Contracts\Filesystem\Filesystem createLocalDriver(array $config, string $name) * @method static \Illuminate\Contracts\Filesystem\Filesystem createFtpDriver(array $config) * @method static \Illuminate\Contracts\Filesystem\Filesystem createSftpDriver(array $config) * @method static \Illuminate\Contracts\Filesystem\Cloud createS3Driver(array $config) @@ -54,6 +54,7 @@ * @method static bool directoryMissing(string $path) * @method static array|null json(string $path, int $flags = 0) * @method static \Symfony\Component\HttpFoundation\StreamedResponse response(string $path, string|null $name = null, array $headers = [], string|null $disposition = 'inline') + * @method static \Symfony\Component\HttpFoundation\StreamedResponse serve(\Illuminate\Http\Request $request, string $path, string|null $name = null, array $headers = []) * @method static \Symfony\Component\HttpFoundation\StreamedResponse download(string $path, string|null $name = null, array $headers = []) * @method static string|false checksum(string $path, array $options = []) * @method static string|false mimeType(string $path) @@ -64,6 +65,7 @@ * @method static \League\Flysystem\FilesystemOperator getDriver() * @method static \League\Flysystem\FilesystemAdapter getAdapter() * @method static array getConfig() + * @method static void serveUsing(\Closure $callback) * @method static void buildTemporaryUrlsUsing(\Closure $callback) * @method static \Illuminate\Filesystem\FilesystemAdapter|mixed when(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) * @method static \Illuminate\Filesystem\FilesystemAdapter|mixed unless(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) From 77739f6154a97b1ee684a70bc1e9e2a57eb2dc17 Mon Sep 17 00:00:00 2001 From: Tijmen Wierenga Date: Wed, 11 Sep 2024 22:15:17 +0200 Subject: [PATCH 043/100] Add tag attribute (#52743) --- src/Illuminate/Container/Attributes/Tag.php | 30 +++++++++++++++++++ .../ContextualAttributeBindingTest.php | 16 ++++++++++ 2 files changed, 46 insertions(+) create mode 100644 src/Illuminate/Container/Attributes/Tag.php diff --git a/src/Illuminate/Container/Attributes/Tag.php b/src/Illuminate/Container/Attributes/Tag.php new file mode 100644 index 000000000000..944fcd994f26 --- /dev/null +++ b/src/Illuminate/Container/Attributes/Tag.php @@ -0,0 +1,30 @@ +tagged($attribute->tag); + } +} diff --git a/tests/Container/ContextualAttributeBindingTest.php b/tests/Container/ContextualAttributeBindingTest.php index 4b99e20fe51e..f13e16599797 100644 --- a/tests/Container/ContextualAttributeBindingTest.php +++ b/tests/Container/ContextualAttributeBindingTest.php @@ -15,7 +15,9 @@ use Illuminate\Container\Attributes\Database; use Illuminate\Container\Attributes\Log; use Illuminate\Container\Attributes\Storage; +use Illuminate\Container\Attributes\Tag; use Illuminate\Container\Container; +use Illuminate\Container\RewindableGenerator; use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract; use Illuminate\Contracts\Auth\Guard as GuardContract; use Illuminate\Contracts\Container\ContextualAttribute; @@ -235,6 +237,20 @@ public function testAttributeOnAppCall() $this->assertEquals('Europe/Paris', $value); } + + public function testTagAttribute() + { + $container = new Container; + $container->bind('one', fn (): int => 1); + $container->bind('two', fn (): int => 2); + $container->tag(['one', 'two'], 'numbers'); + + $value = $container->call(function (#[Tag('numbers')] RewindableGenerator $integers) { + return $integers; + }); + + $this->assertEquals([1, 2], iterator_to_array($value)); + } } #[Attribute(Attribute::TARGET_PARAMETER)] From c65725dc906bfdb849e9aad521e235aefa60faea Mon Sep 17 00:00:00 2001 From: Seth Phat Date: Thu, 12 Sep 2024 03:16:26 +0700 Subject: [PATCH 044/100] Add doc (#52739) --- src/Illuminate/Foundation/Bus/PendingDispatch.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Illuminate/Foundation/Bus/PendingDispatch.php b/src/Illuminate/Foundation/Bus/PendingDispatch.php index ae5136c60b6d..97a339a00946 100644 --- a/src/Illuminate/Foundation/Bus/PendingDispatch.php +++ b/src/Illuminate/Foundation/Bus/PendingDispatch.php @@ -38,7 +38,7 @@ public function __construct($job) /** * Set the desired connection for the job. * - * @param string|null $connection + * @param \BackedEnum|string|null $connection * @return $this */ public function onConnection($connection) @@ -51,7 +51,7 @@ public function onConnection($connection) /** * Set the desired queue for the job. * - * @param string|null $queue + * @param \BackedEnum|string|null $queue * @return $this */ public function onQueue($queue) @@ -64,7 +64,7 @@ public function onQueue($queue) /** * Set the desired connection for the chain. * - * @param string|null $connection + * @param \BackedEnum|string|null $connection * @return $this */ public function allOnConnection($connection) @@ -77,7 +77,7 @@ public function allOnConnection($connection) /** * Set the desired queue for the chain. * - * @param string|null $queue + * @param \BackedEnum|string|null $queue * @return $this */ public function allOnQueue($queue) From d9c322d40ae162f76b30ca3a526a1868f0a8e124 Mon Sep 17 00:00:00 2001 From: Dan Matthews Date: Wed, 11 Sep 2024 21:37:13 +0100 Subject: [PATCH 045/100] New when() helper. (#52665) * feat(blade): New @when directive. * StyleCI * fix: move to compilesConditionals + remove default expression * fix: styleCI * fix: when() returns the value, rather than echoing it. * fix: Remove @throws tags * fix: styleCI * fix: escaping on by default * fix: remove blade directive and adds tests for when helper * StyleCI * remove default, add more tests. * Remove return type * StyleCI * Update helpers.php * move function --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/helpers.php | 18 ++++++++++++++++++ tests/Support/SupportHelpersTest.php | 14 ++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/Illuminate/Collections/helpers.php b/src/Illuminate/Collections/helpers.php index 4150c38ef621..58dcecfb1452 100644 --- a/src/Illuminate/Collections/helpers.php +++ b/src/Illuminate/Collections/helpers.php @@ -236,3 +236,21 @@ function value($value, ...$args) return $value instanceof Closure ? $value(...$args) : $value; } } + +if (! function_exists('when')) { + /** + * Output a value if the given condition is true. + * + * @param mixed $condition + * @param \Closure|mixed $output + * @return mixed + */ + function when($condition, $output) + { + if ($condition) { + return value($output); + } + + return null; + } +} diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 234385e38e12..69877bfb3a2a 100644 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -100,6 +100,20 @@ public function testClassBasename() $this->assertSame('..', class_basename('\Foo\Bar\Baz\\..\\')); } + public function testWhen() + { + $this->assertEquals('Hello', when(true, 'Hello')); + $this->assertEquals(null, when(false, 'Hello')); + $this->assertEquals('There', when(1 === 1, 'There')); // strict types + $this->assertEquals('There', when(1 == '1', 'There')); // loose types + $this->assertEquals(null, when(1 == 2, 'There')); + $this->assertEquals(null, when('1', fn () => null)); + $this->assertEquals(null, when(0, fn () => null)); + $this->assertEquals('True', when([1, 2, 3, 4], 'True')); // Array + $this->assertEquals(null, when([], 'True')); // Empty Array = Falsy + $this->assertEquals('True', when(new StdClass, fn () => 'True')); // Object + } + public function testFilled() { $this->assertFalse(filled(null)); From dcb81632b40ba82646a4f38c5dfd1131f3f44101 Mon Sep 17 00:00:00 2001 From: Kennedy Tedesco Date: Wed, 11 Sep 2024 18:13:36 -0300 Subject: [PATCH 046/100] [11.x] Add `fromUrl()` to Attachment (#52688) * [11.x] Add `fromUrl()` to Attachment * CS Fixes * formatting --------- Co-authored-by: Tim MacDonald --- src/Illuminate/Mail/Attachment.php | 11 +++++++++++ tests/Mail/AttachableTest.php | 29 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/Illuminate/Mail/Attachment.php b/src/Illuminate/Mail/Attachment.php index aa802c9ce85a..cdf601e8c02f 100644 --- a/src/Illuminate/Mail/Attachment.php +++ b/src/Illuminate/Mail/Attachment.php @@ -55,6 +55,17 @@ public static function fromPath($path) return new static(fn ($attachment, $pathStrategy) => $pathStrategy($path, $attachment)); } + /** + * Create a mail attachment from a URL. + * + * @param string $url + * @return static + */ + public static function fromUrl($url) + { + return static::fromPath($url); + } + /** * Create a mail attachment from in-memory data. * diff --git a/tests/Mail/AttachableTest.php b/tests/Mail/AttachableTest.php index caaa6bac9539..d6e928c7f2b6 100644 --- a/tests/Mail/AttachableTest.php +++ b/tests/Mail/AttachableTest.php @@ -109,4 +109,33 @@ public function toMailAttachment() 99, ], $notification->dataArgs); } + + public function testFromUrlMethod() + { + $mailable = new class extends Mailable + { + public function build() + { + $this->attach(new class implements Attachable + { + public function toMailAttachment() + { + return Attachment::fromUrl('https://example.com/file.pdf') + ->as('example.pdf') + ->withMime('application/pdf'); + } + }); + } + }; + + $mailable->build(); + + $this->assertSame([ + 'file' => 'https://example.com/file.pdf', + 'options' => [ + 'as' => 'example.pdf', + 'mime' => 'application/pdf', + ], + ], $mailable->attachments[0]); + } } From 576f6f5d63f68afb36dc062e728e717ddeb1a4aa Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 11 Sep 2024 21:21:26 +0000 Subject: [PATCH 047/100] Update version to v11.23.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 7c82367b67ea..0443350f6008 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.22.0'; + const VERSION = '11.23.0'; /** * The base path for the Laravel installation. From 02caa90584bc39a325ddf6c6488ef7be0f8b3aa7 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 11 Sep 2024 21:23:06 +0000 Subject: [PATCH 048/100] Update CHANGELOG --- CHANGELOG.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5b48cdd4347..28c7c1c0a5f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,41 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.22.0...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.23.0...11.x) + +## [v11.23.0](https://github.com/laravel/framework/compare/v11.22.0...v11.23.0) - 2024-09-11 + +* [11.x] Fix $fail closure type in docblocks for validation rules by [@bastien-phi](https://github.com/bastien-phi) in https://github.com/laravel/framework/pull/52644 +* [11.x] Add MSSQL 2017 and PGSQL 10 builds by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/52631 +* Update `everyThirtyMinutes` cron expression by [@SamuelNitsche](https://github.com/SamuelNitsche) in https://github.com/laravel/framework/pull/52662 +* Bump micromatch from 4.0.5 to 4.0.8 in /src/Illuminate/Foundation/resources/exceptions/renderer by [@dependabot](https://github.com/dependabot) in https://github.com/laravel/framework/pull/52664 +* [11.x] apply excludeUnvalidatedArrayKeys to list validation by [@lorenzolosa](https://github.com/lorenzolosa) in https://github.com/laravel/framework/pull/52658 +* [11.x] Adding minRatio & maxRatio rules on Dimension validation ruleset by [@CamKem](https://github.com/CamKem) in https://github.com/laravel/framework/pull/52482 +* [11.x] Add BackedEnum support to Authorize middleware by [@diaafares](https://github.com/diaafares) in https://github.com/laravel/framework/pull/52679 +* [11.x] Add BackedEnum support to Gate methods by [@diaafares](https://github.com/diaafares) in https://github.com/laravel/framework/pull/52677 +* [11.x] Suggest serializable-closure by [@driesvints](https://github.com/driesvints) in https://github.com/laravel/framework/pull/52673 +* [11.x] Fix alter table expressions on SQLite by [@hafezdivandari](https://github.com/hafezdivandari) in https://github.com/laravel/framework/pull/52678 +* [11.x] Add Exceptions\Handler::mapLogLevel(...) so the logic can be easily overridden by [@taka-oyama](https://github.com/taka-oyama) in https://github.com/laravel/framework/pull/52666 +* [11.x] Bugfix for calling pluck() on chaperoned relations. by [@samlev](https://github.com/samlev) in https://github.com/laravel/framework/pull/52680 +* [11.x] Fix build failures due to enum collide After adding BackedEnum support to Gate by [@diaafares](https://github.com/diaafares) in https://github.com/laravel/framework/pull/52683 +* Fixing Str::trim to remove the default trim/ltrim/rtim characters " \n\r\t\v\0" by [@mathiasgrimm](https://github.com/mathiasgrimm) in https://github.com/laravel/framework/pull/52684 +* [11.x] Add `Skip` middleware for Queue Jobs by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/52645 +* [11.x] Fix etag headers for binary file responses by [@wouterrutgers](https://github.com/wouterrutgers) in https://github.com/laravel/framework/pull/52705 +* [11.x] add `withoutDelay()` to PendingDispatch by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/52696 +* [11.x] Refactor `Container::getInstance()` to use null coalescing assignment by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/52693 +* [11.x] Removed unnecessary call to setAccessible(true) by [@xurshudyan](https://github.com/xurshudyan) in https://github.com/laravel/framework/pull/52691 +* [11.x] Add `Eloquent\Collection::findOrFail` by [@stevebauman](https://github.com/stevebauman) in https://github.com/laravel/framework/pull/52690 +* [11.x] PHPStan Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52712 +* [11.x] Fix Collection PHPDoc by [@staudenmeir](https://github.com/staudenmeir) in https://github.com/laravel/framework/pull/52724 +* [11.x] Add optional parameter for `confirmed` validator rule by [@jwpage](https://github.com/jwpage) in https://github.com/laravel/framework/pull/52722 +* [11.x] Test Improvements by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52718 +* [11.x] Fix incorrect variable-length argument `$guards` from array to string by [@kayw-geek](https://github.com/kayw-geek) in https://github.com/laravel/framework/pull/52719 +* Allow testing of relative signed routes by [@shealavington](https://github.com/shealavington) in https://github.com/laravel/framework/pull/52726 +* [11.x] fix: Builder::with closure types by [@calebdw](https://github.com/calebdw) in https://github.com/laravel/framework/pull/52729 +* Laracon 2024 by [@taylorotwell](https://github.com/taylorotwell) in https://github.com/laravel/framework/pull/52710 +* Add `Tag` attribute by [@TijmenWierenga](https://github.com/TijmenWierenga) in https://github.com/laravel/framework/pull/52743 +* [11.x] Adds BackedEnum to PendingDispatch's phpDoc for onQueue, allOnQueue, onConnection, allOnConnection methods by [@sethsandaru](https://github.com/sethsandaru) in https://github.com/laravel/framework/pull/52739 +* New when() helper. by [@danmatthews](https://github.com/danmatthews) in https://github.com/laravel/framework/pull/52665 +* [11.x] Add `fromUrl()` to Attachment by [@KennedyTedesco](https://github.com/KennedyTedesco) in https://github.com/laravel/framework/pull/52688 ## [v11.22.0](https://github.com/laravel/framework/compare/v11.21.0...v11.22.0) - 2024-09-03 From e840144735fd6847381fb64d712a74956dfbb343 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 11 Sep 2024 16:41:38 -0500 Subject: [PATCH 049/100] fix comparison --- src/Illuminate/Validation/Concerns/ValidatesAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php index d97aed643a07..5628e43783c9 100644 --- a/src/Illuminate/Validation/Concerns/ValidatesAttributes.php +++ b/src/Illuminate/Validation/Concerns/ValidatesAttributes.php @@ -498,7 +498,7 @@ public function validateBoolean($attribute, $value) */ public function validateConfirmed($attribute, $value, $parameters) { - return $this->validateSame($attribute, $value, [$parameters[0] ?: $attribute.'_confirmation']); + return $this->validateSame($attribute, $value, [$parameters[0] ?? $attribute.'_confirmation']); } /** From 9121cc927c8689765cdf25ac530a00d61a2716e9 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 11 Sep 2024 21:43:58 +0000 Subject: [PATCH 050/100] Update version to v11.23.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 0443350f6008..c2a73e72bd48 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.23.0'; + const VERSION = '11.23.1'; /** * The base path for the Laravel installation. From 1c1aeb52e5bf3b128665260312a099d1d971b417 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 11 Sep 2024 21:45:51 +0000 Subject: [PATCH 051/100] Update CHANGELOG --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28c7c1c0a5f0..4721362cdc54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.23.0...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.23.1...11.x) + +## [v11.23.1](https://github.com/laravel/framework/compare/v11.23.0...v11.23.1) - 2024-09-11 ## [v11.23.0](https://github.com/laravel/framework/compare/v11.22.0...v11.23.0) - 2024-09-11 From f8c493c7b832f27d8d3846d500893186cc92ec39 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 11 Sep 2024 16:58:12 -0500 Subject: [PATCH 052/100] default value --- src/Illuminate/Filesystem/FilesystemManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Filesystem/FilesystemManager.php b/src/Illuminate/Filesystem/FilesystemManager.php index 0fa06eaf6d1f..5c23c5b811d1 100644 --- a/src/Illuminate/Filesystem/FilesystemManager.php +++ b/src/Illuminate/Filesystem/FilesystemManager.php @@ -170,7 +170,7 @@ protected function callCustomCreator(array $config) * @param string $name * @return \Illuminate\Contracts\Filesystem\Filesystem */ - public function createLocalDriver(array $config, string $name) + public function createLocalDriver(array $config, string $name = 'local') { $visibility = PortableVisibilityConverter::fromArray( $config['permissions'] ?? [], From a3d6a5bd38ab07f8e78360f7aaaf4a7e226cc1d1 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 11 Sep 2024 21:58:48 +0000 Subject: [PATCH 053/100] Update facade docblocks --- src/Illuminate/Support/Facades/Storage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Storage.php b/src/Illuminate/Support/Facades/Storage.php index f2dc0d77912b..bdfe83fbbcbe 100644 --- a/src/Illuminate/Support/Facades/Storage.php +++ b/src/Illuminate/Support/Facades/Storage.php @@ -9,7 +9,7 @@ * @method static \Illuminate\Contracts\Filesystem\Filesystem disk(string|null $name = null) * @method static \Illuminate\Contracts\Filesystem\Cloud cloud() * @method static \Illuminate\Contracts\Filesystem\Filesystem build(string|array $config) - * @method static \Illuminate\Contracts\Filesystem\Filesystem createLocalDriver(array $config, string $name) + * @method static \Illuminate\Contracts\Filesystem\Filesystem createLocalDriver(array $config, string $name = 'local') * @method static \Illuminate\Contracts\Filesystem\Filesystem createFtpDriver(array $config) * @method static \Illuminate\Contracts\Filesystem\Filesystem createSftpDriver(array $config) * @method static \Illuminate\Contracts\Filesystem\Cloud createS3Driver(array $config) From d38bf0fd3a8936e1cb9ca8eb8d7304a564f790f3 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 11 Sep 2024 21:59:23 +0000 Subject: [PATCH 054/100] Update version to v11.23.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 c2a73e72bd48..dbd862f6cbc0 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.23.1'; + const VERSION = '11.23.2'; /** * The base path for the Laravel installation. From 042c267059bc8aa284af5579d3f6e69624b9519a Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Wed, 11 Sep 2024 22:01:03 +0000 Subject: [PATCH 055/100] Update CHANGELOG --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4721362cdc54..79b3bc6044d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.23.1...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.23.2...11.x) + +## [v11.23.2](https://github.com/laravel/framework/compare/v11.23.1...v11.23.2) - 2024-09-11 ## [v11.23.1](https://github.com/laravel/framework/compare/v11.23.0...v11.23.1) - 2024-09-11 From dd64ab7c2fb28ca8ae2f6309dc32136a0d91c8b8 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 12 Sep 2024 20:32:25 +0800 Subject: [PATCH 056/100] [11.x] Fixed attempt to call `Application::routesAreCached()` when application doesn't implements `CachesRoutes` contract. (#52761) fixes #52760 Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Filesystem/FilesystemServiceProvider.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Filesystem/FilesystemServiceProvider.php b/src/Illuminate/Filesystem/FilesystemServiceProvider.php index a6ad8e1a7b34..28fe5f60b23a 100644 --- a/src/Illuminate/Filesystem/FilesystemServiceProvider.php +++ b/src/Illuminate/Filesystem/FilesystemServiceProvider.php @@ -2,6 +2,7 @@ namespace Illuminate\Filesystem; +use Illuminate\Contracts\Foundation\CachesRoutes; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; @@ -78,7 +79,7 @@ protected function registerManager() */ protected function serveFiles() { - if ($this->app->routesAreCached()) { + if ($this->app instanceof CachesRoutes && $this->app->routesAreCached()) { return; } From 0ecdd717d3d655f983d81ca54b6f167e08ef4e74 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Thu, 12 Sep 2024 12:32:51 +0000 Subject: [PATCH 057/100] Update facade docblocks --- src/Illuminate/Support/Facades/App.php | 2 +- src/Illuminate/Support/Facades/Auth.php | 2 +- src/Illuminate/Support/Facades/Cache.php | 4 ++-- src/Illuminate/Support/Facades/Config.php | 2 +- src/Illuminate/Support/Facades/Context.php | 2 +- src/Illuminate/Support/Facades/Cookie.php | 2 +- src/Illuminate/Support/Facades/DB.php | 2 +- src/Illuminate/Support/Facades/Event.php | 2 +- src/Illuminate/Support/Facades/File.php | 2 +- src/Illuminate/Support/Facades/Http.php | 2 +- src/Illuminate/Support/Facades/Lang.php | 2 +- src/Illuminate/Support/Facades/Mail.php | 2 +- src/Illuminate/Support/Facades/Notification.php | 2 +- src/Illuminate/Support/Facades/Process.php | 2 +- src/Illuminate/Support/Facades/Redirect.php | 2 +- src/Illuminate/Support/Facades/Redis.php | 2 +- src/Illuminate/Support/Facades/Request.php | 2 +- src/Illuminate/Support/Facades/Response.php | 2 +- src/Illuminate/Support/Facades/Route.php | 4 ++-- src/Illuminate/Support/Facades/Schema.php | 2 +- src/Illuminate/Support/Facades/Session.php | 2 +- src/Illuminate/Support/Facades/Storage.php | 2 +- src/Illuminate/Support/Facades/URL.php | 2 +- src/Illuminate/Support/Facades/View.php | 2 +- src/Illuminate/Support/Facades/Vite.php | 2 +- 25 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Illuminate/Support/Facades/App.php b/src/Illuminate/Support/Facades/App.php index 77ca41fc5897..7d8a7481730b 100755 --- a/src/Illuminate/Support/Facades/App.php +++ b/src/Illuminate/Support/Facades/App.php @@ -137,7 +137,7 @@ * @method static void forgetScopedInstances() * @method static \Illuminate\Foundation\Application getInstance() * @method static \Illuminate\Contracts\Container\Container|\Illuminate\Foundation\Application setInstance(\Illuminate\Contracts\Container\Container|null $container = null) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Auth.php b/src/Illuminate/Support/Facades/Auth.php index fb0165795baa..32b2eb64aa95 100755 --- a/src/Illuminate/Support/Facades/Auth.php +++ b/src/Illuminate/Support/Facades/Auth.php @@ -59,7 +59,7 @@ * @method static \Illuminate\Auth\SessionGuard forgetUser() * @method static \Illuminate\Contracts\Auth\UserProvider getProvider() * @method static void setProvider(\Illuminate\Contracts\Auth\UserProvider $provider) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Cache.php b/src/Illuminate/Support/Facades/Cache.php index e5e802764d4c..eb5d8d79e48a 100755 --- a/src/Illuminate/Support/Facades/Cache.php +++ b/src/Illuminate/Support/Facades/Cache.php @@ -31,7 +31,7 @@ * @method static mixed remember(string $key, \Closure|\DateTimeInterface|\DateInterval|int|null $ttl, \Closure $callback) * @method static mixed sear(string $key, \Closure $callback) * @method static mixed rememberForever(string $key, \Closure $callback) - * @method static mixed flexible(string $key, void $ttl, callable $callback, void $lock = null) + * @method static mixed flexible(string $key, array $ttl, callable $callback, array|null $lock = null) * @method static bool forget(string $key) * @method static bool delete(string $key) * @method static bool deleteMultiple(iterable $keys) @@ -44,7 +44,7 @@ * @method static \Illuminate\Cache\Repository setStore(\Illuminate\Contracts\Cache\Store $store) * @method static \Illuminate\Contracts\Events\Dispatcher|null getEventDispatcher() * @method static void setEventDispatcher(\Illuminate\Contracts\Events\Dispatcher $events) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Config.php b/src/Illuminate/Support/Facades/Config.php index 3fe3bac716c4..c4f17981cc9e 100755 --- a/src/Illuminate/Support/Facades/Config.php +++ b/src/Illuminate/Support/Facades/Config.php @@ -15,7 +15,7 @@ * @method static void prepend(string $key, mixed $value) * @method static void push(string $key, mixed $value) * @method static array all() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Context.php b/src/Illuminate/Support/Facades/Context.php index 52290f6aa376..090992745a9e 100644 --- a/src/Illuminate/Support/Facades/Context.php +++ b/src/Illuminate/Support/Facades/Context.php @@ -30,7 +30,7 @@ * @method static \Illuminate\Log\Context\Repository flush() * @method static \Illuminate\Log\Context\Repository|mixed when(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) * @method static \Illuminate\Log\Context\Repository|mixed unless(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Cookie.php b/src/Illuminate/Support/Facades/Cookie.php index ab0b9f73f457..3592f5839e74 100755 --- a/src/Illuminate/Support/Facades/Cookie.php +++ b/src/Illuminate/Support/Facades/Cookie.php @@ -14,7 +14,7 @@ * @method static \Illuminate\Cookie\CookieJar setDefaultPathAndDomain(string $path, string|null $domain, bool|null $secure = false, string|null $sameSite = null) * @method static \Symfony\Component\HttpFoundation\Cookie[] getQueuedCookies() * @method static \Illuminate\Cookie\CookieJar flushQueuedCookies() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/DB.php b/src/Illuminate/Support/Facades/DB.php index da0f04418d26..7b8f1e36195f 100644 --- a/src/Illuminate/Support/Facades/DB.php +++ b/src/Illuminate/Support/Facades/DB.php @@ -23,7 +23,7 @@ * @method static array getConnections() * @method static void setReconnector(callable $reconnector) * @method static \Illuminate\Database\DatabaseManager setApplication(\Illuminate\Contracts\Foundation\Application $app) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Event.php b/src/Illuminate/Support/Facades/Event.php index 76031ce9d6fd..f6e17b695397 100755 --- a/src/Illuminate/Support/Facades/Event.php +++ b/src/Illuminate/Support/Facades/Event.php @@ -22,7 +22,7 @@ * @method static \Illuminate\Events\Dispatcher setQueueResolver(callable $resolver) * @method static \Illuminate\Events\Dispatcher setTransactionManagerResolver(callable $resolver) * @method static array getRawListeners() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/File.php b/src/Illuminate/Support/Facades/File.php index 2ef76942a4ee..b28cc6c72d63 100755 --- a/src/Illuminate/Support/Facades/File.php +++ b/src/Illuminate/Support/Facades/File.php @@ -51,7 +51,7 @@ * @method static bool cleanDirectory(string $directory) * @method static \Illuminate\Filesystem\Filesystem|mixed when(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) * @method static \Illuminate\Filesystem\Filesystem|mixed unless(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Http.php b/src/Illuminate/Support/Facades/Http.php index 34747a1ebc04..11b1bbf99503 100644 --- a/src/Illuminate/Support/Facades/Http.php +++ b/src/Illuminate/Support/Facades/Http.php @@ -23,7 +23,7 @@ * @method static \Illuminate\Http\Client\PendingRequest createPendingRequest() * @method static \Illuminate\Contracts\Events\Dispatcher|null getDispatcher() * @method static array getGlobalMiddleware() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Lang.php b/src/Illuminate/Support/Facades/Lang.php index c2a52c48d933..a341b5fab640 100755 --- a/src/Illuminate/Support/Facades/Lang.php +++ b/src/Illuminate/Support/Facades/Lang.php @@ -26,7 +26,7 @@ * @method static void stringable(callable|string $class, callable|null $handler = null) * @method static void setParsedKey(string $key, array $parsed) * @method static void flushParsedKeys() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Mail.php b/src/Illuminate/Support/Facades/Mail.php index af245fab23b6..8987aa04cc3a 100755 --- a/src/Illuminate/Support/Facades/Mail.php +++ b/src/Illuminate/Support/Facades/Mail.php @@ -37,7 +37,7 @@ * @method static \Illuminate\Contracts\View\Factory getViewFactory() * @method static void setSymfonyTransport(\Symfony\Component\Mailer\Transport\TransportInterface $transport) * @method static \Illuminate\Mail\Mailer setQueue(\Illuminate\Contracts\Queue\Factory $queue) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Notification.php b/src/Illuminate/Support/Facades/Notification.php index e1440d523f4d..8b30997e7923 100644 --- a/src/Illuminate/Support/Facades/Notification.php +++ b/src/Illuminate/Support/Facades/Notification.php @@ -33,7 +33,7 @@ * @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, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Process.php b/src/Illuminate/Support/Facades/Process.php index 3831f9f48964..4f3546d07c00 100644 --- a/src/Illuminate/Support/Facades/Process.php +++ b/src/Illuminate/Support/Facades/Process.php @@ -38,7 +38,7 @@ * @method static \Illuminate\Contracts\Process\ProcessResult pipe(callable|array $callback, callable|null $output = null) * @method static \Illuminate\Process\ProcessPoolResults concurrently(callable $callback, callable|null $output = null) * @method static \Illuminate\Process\PendingProcess newPendingProcess() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Redirect.php b/src/Illuminate/Support/Facades/Redirect.php index 8884fe9af989..905c14aa90eb 100755 --- a/src/Illuminate/Support/Facades/Redirect.php +++ b/src/Illuminate/Support/Facades/Redirect.php @@ -18,7 +18,7 @@ * @method static void setSession(\Illuminate\Session\Store $session) * @method static string|null getIntendedUrl() * @method static \Illuminate\Routing\Redirector setIntendedUrl(string $url) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Redis.php b/src/Illuminate/Support/Facades/Redis.php index 1579b867cd1d..b796ad09d325 100755 --- a/src/Illuminate/Support/Facades/Redis.php +++ b/src/Illuminate/Support/Facades/Redis.php @@ -24,7 +24,7 @@ * @method static \Illuminate\Contracts\Events\Dispatcher getEventDispatcher() * @method static void setEventDispatcher(\Illuminate\Contracts\Events\Dispatcher $events) * @method static void unsetEventDispatcher() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Request.php b/src/Illuminate/Support/Facades/Request.php index 5410b4f21c11..e5c6affccb54 100755 --- a/src/Illuminate/Support/Facades/Request.php +++ b/src/Illuminate/Support/Facades/Request.php @@ -172,7 +172,7 @@ * @method static \Illuminate\Http\UploadedFile|\Illuminate\Http\UploadedFile[]|array|null file(string|null $key = null, mixed $default = null) * @method static \Illuminate\Http\Request dump(mixed $keys = []) * @method static never dd(mixed ...$args) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Response.php b/src/Illuminate/Support/Facades/Response.php index 7d18d69aaf97..a5addbfaa6bf 100755 --- a/src/Illuminate/Support/Facades/Response.php +++ b/src/Illuminate/Support/Facades/Response.php @@ -20,7 +20,7 @@ * @method static \Illuminate\Http\RedirectResponse redirectToAction(array|string $action, mixed $parameters = [], int $status = 302, array $headers = []) * @method static \Illuminate\Http\RedirectResponse redirectGuest(string $path, int $status = 302, array $headers = [], bool|null $secure = null) * @method static \Illuminate\Http\RedirectResponse redirectToIntended(string $default = '/', int $status = 302, array $headers = [], bool|null $secure = null) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Route.php b/src/Illuminate/Support/Facades/Route.php index e108078f3a68..3e2078357e57 100755 --- a/src/Illuminate/Support/Facades/Route.php +++ b/src/Illuminate/Support/Facades/Route.php @@ -75,12 +75,12 @@ * @method static void setCompiledRoutes(array $routes) * @method static array uniqueMiddleware(array $middleware) * @method static \Illuminate\Routing\Router setContainer(\Illuminate\Container\Container $container) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() * @method static mixed macroCall(string $method, array $parameters) - * @method static void tap(callable|null $callback = null) + * @method static \Illuminate\Support\HigherOrderTapProxy|\Illuminate\Routing\Router tap(callable|null $callback = null) * @method static \Illuminate\Routing\RouteRegistrar attribute(string $key, mixed $value) * @method static \Illuminate\Routing\RouteRegistrar whereAlpha(array|string $parameters) * @method static \Illuminate\Routing\RouteRegistrar whereAlphaNumeric(array|string $parameters) diff --git a/src/Illuminate/Support/Facades/Schema.php b/src/Illuminate/Support/Facades/Schema.php index d4d39f9f0521..a7feff2e7045 100755 --- a/src/Illuminate/Support/Facades/Schema.php +++ b/src/Illuminate/Support/Facades/Schema.php @@ -41,7 +41,7 @@ * @method static \Illuminate\Database\Connection getConnection() * @method static \Illuminate\Database\Schema\Builder setConnection(\Illuminate\Database\Connection $connection) * @method static void blueprintResolver(\Closure $resolver) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Session.php b/src/Illuminate/Support/Facades/Session.php index f51fe423c7ce..95f72f5c250d 100755 --- a/src/Illuminate/Support/Facades/Session.php +++ b/src/Illuminate/Support/Facades/Session.php @@ -64,7 +64,7 @@ * @method static \SessionHandlerInterface setHandler(\SessionHandlerInterface $handler) * @method static bool handlerNeedsRequest() * @method static void setRequestOnHandler(\Illuminate\Http\Request $request) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Storage.php b/src/Illuminate/Support/Facades/Storage.php index bdfe83fbbcbe..0ed8e1e49050 100644 --- a/src/Illuminate/Support/Facades/Storage.php +++ b/src/Illuminate/Support/Facades/Storage.php @@ -69,7 +69,7 @@ * @method static void buildTemporaryUrlsUsing(\Closure $callback) * @method static \Illuminate\Filesystem\FilesystemAdapter|mixed when(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) * @method static \Illuminate\Filesystem\FilesystemAdapter|mixed unless(\Closure|mixed|null $value = null, callable|null $callback = null, callable|null $default = null) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/URL.php b/src/Illuminate/Support/Facades/URL.php index ea1afcd8450e..6e964ea9ca07 100755 --- a/src/Illuminate/Support/Facades/URL.php +++ b/src/Illuminate/Support/Facades/URL.php @@ -43,7 +43,7 @@ * @method static \Illuminate\Routing\UrlGenerator resolveMissingNamedRoutesUsing(callable $missingNamedRouteResolver) * @method static string getRootControllerNamespace() * @method static \Illuminate\Routing\UrlGenerator setRootControllerNamespace(string $rootNamespace) - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/View.php b/src/Illuminate/Support/Facades/View.php index 787dc80b3654..10eaa645d548 100755 --- a/src/Illuminate/Support/Facades/View.php +++ b/src/Illuminate/Support/Facades/View.php @@ -35,7 +35,7 @@ * @method static void setContainer(\Illuminate\Contracts\Container\Container $container) * @method static mixed shared(string $key, mixed $default = null) * @method static array getShared() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() diff --git a/src/Illuminate/Support/Facades/Vite.php b/src/Illuminate/Support/Facades/Vite.php index 8dccfdcd54f8..065cd71a651f 100644 --- a/src/Illuminate/Support/Facades/Vite.php +++ b/src/Illuminate/Support/Facades/Vite.php @@ -26,7 +26,7 @@ * @method static string|null manifestHash(string|null $buildDirectory = null) * @method static bool isRunningHot() * @method static string toHtml() - * @method static void macro(string $name, object|callable $macro, object|callable $macro = null) + * @method static void macro(string $name, object|callable $macro) * @method static void mixin(object $mixin, bool $replace = true) * @method static bool hasMacro(string $name) * @method static void flushMacros() From 6a547e460979efe3804f4322b85bfcd1cee440a6 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Thu, 12 Sep 2024 16:03:31 +0330 Subject: [PATCH 058/100] [11.x] Fix PHP and Artisan binary (#52744) * fix php and artisan binary * formatting * force re-run tests * formatting * formatting * formatting * formatting * Revert "formatting" This reverts commit 4d53aa93a640d7868a78531ad86117f3738911f7. * require illuminate/console * Reapply "formatting" This reverts commit ddc3264066e521e1634d4f02ccef760dda9917b6. * Revert "require illuminate/console" This reverts commit b55e7739fdafcb9290648f085535bd28dc008caf. --- src/Illuminate/Concurrency/ProcessDriver.php | 38 ++++++++++++++++--- src/Illuminate/Console/Application.php | 2 +- .../Scheduling/ScheduleWorkCommand.php | 7 +--- .../Console/BroadcastingInstallCommand.php | 4 +- .../Foundation/Console/ServeCommand.php | 2 +- src/Illuminate/Queue/Listener.php | 2 +- src/Illuminate/Support/Composer.php | 2 +- 7 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/Illuminate/Concurrency/ProcessDriver.php b/src/Illuminate/Concurrency/ProcessDriver.php index 0cd0add93ff1..19dd392fa41e 100644 --- a/src/Illuminate/Concurrency/ProcessDriver.php +++ b/src/Illuminate/Concurrency/ProcessDriver.php @@ -25,13 +25,18 @@ public function __construct(protected ProcessFactory $processFactory) */ public function run(Closure|array $tasks): array { - $php = (new PhpExecutableFinder)->find(false); + $php = $this->phpBinary(); + $artisan = $this->artisanBinary(); - $results = $this->processFactory->pool(function (Pool $pool) use ($tasks, $php) { + $results = $this->processFactory->pool(function (Pool $pool) use ($tasks, $php, $artisan) { foreach (Arr::wrap($tasks) as $task) { $pool->path(base_path())->env([ 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), - ])->command($php.' artisan invoke-serialized-closure'); + ])->command([ + $php, + $artisan, + 'invoke-serialized-closure', + ]); } })->start()->wait(); @@ -53,12 +58,35 @@ public function run(Closure|array $tasks): array */ public function defer(Closure|array $tasks): DeferredCallback { - return defer(function () use ($tasks) { + $php = $this->phpBinary(); + $artisan = $this->artisanBinary(); + + return defer(function () use ($tasks, $php, $artisan) { foreach (Arr::wrap($tasks) as $task) { $this->processFactory->path(base_path())->env([ 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), - ])->run('php artisan invoke-serialized-closure 2>&1 &'); + ])->run([ + $php, + $artisan, + 'invoke-serialized-closure 2>&1 &', + ]); } }); } + + /** + * Get the PHP binary. + */ + protected function phpBinary(): string + { + return (new PhpExecutableFinder)->find(false) ?: 'php'; + } + + /** + * Get the Artisan binary. + */ + protected function artisanBinary(): string + { + return defined('ARTISAN_BINARY') ? ARTISAN_BINARY : 'artisan'; + } } diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 303493492e4e..8e92793855fc 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -84,7 +84,7 @@ public function __construct(Container $laravel, Dispatcher $events, $version) */ public static function phpBinary() { - return ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false)); + return ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false) ?: 'php'); } /** diff --git a/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php b/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php index d784b5904fdd..da4dc7374633 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleWorkCommand.php @@ -2,6 +2,7 @@ namespace Illuminate\Console\Scheduling; +use Illuminate\Console\Application; use Illuminate\Console\Command; use Illuminate\Support\Carbon; use Illuminate\Support\ProcessUtils; @@ -40,11 +41,7 @@ public function handle() [$lastExecutionStartedAt, $executions] = [Carbon::now()->subMinutes(10), []]; - $command = implode(' ', array_map(fn ($arg) => ProcessUtils::escapeArgument($arg), [ - PHP_BINARY, - defined('ARTISAN_BINARY') ? ARTISAN_BINARY : 'artisan', - 'schedule:run', - ])); + $command = Application::formatCommandString('schedule:run'); if ($this->option('run-output-file')) { $command .= ' >> '.ProcessUtils::escapeArgument($this->option('run-output-file')).' 2>&1'; diff --git a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php index 6639f9395800..f6b5e4869d08 100644 --- a/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php +++ b/src/Illuminate/Foundation/Console/BroadcastingInstallCommand.php @@ -154,10 +154,8 @@ protected function installReverb() 'laravel/reverb:^1.0', ]); - $php = (new PhpExecutableFinder())->find(false) ?: 'php'; - Process::run([ - $php, + (new PhpExecutableFinder())->find(false) ?: 'php', defined('ARTISAN_BINARY') ? ARTISAN_BINARY : 'artisan', 'reverb:install', ]); diff --git a/src/Illuminate/Foundation/Console/ServeCommand.php b/src/Illuminate/Foundation/Console/ServeCommand.php index ae204bf24c4e..6c95b8233f63 100644 --- a/src/Illuminate/Foundation/Console/ServeCommand.php +++ b/src/Illuminate/Foundation/Console/ServeCommand.php @@ -178,7 +178,7 @@ protected function serverCommand() : __DIR__.'/../resources/server.php'; return [ - (new PhpExecutableFinder)->find(false), + (new PhpExecutableFinder)->find(false) ?: 'php', '-S', $this->host().':'.$this->port(), $server, diff --git a/src/Illuminate/Queue/Listener.php b/src/Illuminate/Queue/Listener.php index f7744b45bb01..58a28ee18284 100755 --- a/src/Illuminate/Queue/Listener.php +++ b/src/Illuminate/Queue/Listener.php @@ -61,7 +61,7 @@ public function __construct($commandPath) */ protected function phpBinary() { - return (new PhpExecutableFinder)->find(false); + return (new PhpExecutableFinder)->find(false) ?: 'php'; } /** diff --git a/src/Illuminate/Support/Composer.php b/src/Illuminate/Support/Composer.php index 08a46d5bd3a9..3725394e017d 100644 --- a/src/Illuminate/Support/Composer.php +++ b/src/Illuminate/Support/Composer.php @@ -204,7 +204,7 @@ protected function findComposerFile() */ protected function phpBinary() { - return (string) (new PhpExecutableFinder)->find(false); + return (new PhpExecutableFinder)->find(false) ?: 'php'; } /** From 53193a218837f800ea1c9e2772dcdbe0fcd282cd Mon Sep 17 00:00:00 2001 From: Caleb White Date: Thu, 12 Sep 2024 08:07:22 -0500 Subject: [PATCH 059/100] fix: PHPDoc for loading nested relations (#52762) --- src/Illuminate/Database/Eloquent/Builder.php | 4 ++-- .../Database/Eloquent/Collection.php | 22 +++++++++---------- types/Database/Eloquent/Builder.php | 2 ++ types/Database/Eloquent/Collection.php | 11 ++++++++++ 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index ff31cd8e4ae3..3a614e88d161 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -1537,7 +1537,7 @@ protected function createNestedWhere($whereSlice, $boolean = 'and') /** * Set the relationships that should be eager loaded. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param (\Closure(\Illuminate\Database\Eloquent\Relations\Relation<*,*,*>): mixed)|string|null $callback * @return $this */ @@ -1572,7 +1572,7 @@ public function without($relations) /** * Set the relationships that should be eager loaded while removing any previously added eager loading specifications. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function withOnly($relations) diff --git a/src/Illuminate/Database/Eloquent/Collection.php b/src/Illuminate/Database/Eloquent/Collection.php index bdce34b0352e..62e84780a7d6 100755 --- a/src/Illuminate/Database/Eloquent/Collection.php +++ b/src/Illuminate/Database/Eloquent/Collection.php @@ -84,7 +84,7 @@ public function findOrFail($key) /** * Load a set of relationships onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function load($relations) @@ -105,7 +105,7 @@ public function load($relations) /** * Load a set of aggregations over relationship's column onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @param string|null $function * @return $this @@ -142,7 +142,7 @@ public function loadAggregate($relations, $column, $function = null) /** * Load a set of relationship counts onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadCount($relations) @@ -153,7 +153,7 @@ public function loadCount($relations) /** * Load a set of relationship's max column values onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -165,7 +165,7 @@ public function loadMax($relations, $column) /** * Load a set of relationship's min column values onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -177,7 +177,7 @@ public function loadMin($relations, $column) /** * Load a set of relationship's column summations onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -189,7 +189,7 @@ public function loadSum($relations, $column) /** * Load a set of relationship's average column values onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @param string $column * @return $this */ @@ -201,7 +201,7 @@ public function loadAvg($relations, $column) /** * Load a set of related existences onto the collection. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadExists($relations) @@ -212,7 +212,7 @@ public function loadExists($relations) /** * Load a set of relationships onto the collection if they are not already eager loaded. * - * @param array): mixed)|string>|string $relations + * @param array): mixed)|string>|string $relations * @return $this */ public function loadMissing($relations) @@ -284,7 +284,7 @@ protected function loadMissingRelation(self $models, array $path) * Load a set of relationships onto the mixed relationship collection. * * @param string $relation - * @param array): mixed)|string> $relations + * @param array): mixed)|string> $relations * @return $this */ public function loadMorph($relation, $relations) @@ -301,7 +301,7 @@ public function loadMorph($relation, $relations) * Load a set of relationship counts onto the mixed relationship collection. * * @param string $relation - * @param array): mixed)|string> $relations + * @param array): mixed)|string> $relations * @return $this */ public function loadMorphCount($relation, $relations) diff --git a/types/Database/Eloquent/Builder.php b/types/Database/Eloquent/Builder.php index a31d0dcacdcf..ba46642414eb 100644 --- a/types/Database/Eloquent/Builder.php +++ b/types/Database/Eloquent/Builder.php @@ -24,11 +24,13 @@ function test( assertType('Illuminate\Database\Eloquent\Builder', $query->orWhere('name', 'John')); assertType('Illuminate\Database\Eloquent\Builder', $query->whereNot('status', 'active')); assertType('Illuminate\Database\Eloquent\Builder', $query->with('relation')); + assertType('Illuminate\Database\Eloquent\Builder', $query->with(['relation' => ['foo' => fn ($q) => $q]])); assertType('Illuminate\Database\Eloquent\Builder', $query->with(['relation' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Builder', $query->without('relation')); assertType('Illuminate\Database\Eloquent\Builder', $query->withOnly(['relation'])); + assertType('Illuminate\Database\Eloquent\Builder', $query->withOnly(['relation' => ['foo' => fn ($q) => $q]])); assertType('Illuminate\Database\Eloquent\Builder', $query->withOnly(['relation' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); diff --git a/types/Database/Eloquent/Collection.php b/types/Database/Eloquent/Collection.php index 1d74831ce6f5..48933d24eef1 100644 --- a/types/Database/Eloquent/Collection.php +++ b/types/Database/Eloquent/Collection.php @@ -10,12 +10,14 @@ assertType('Illuminate\Database\Eloquent\Collection', $collection->load('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->load(['string'])); +assertType('Illuminate\Database\Eloquent\Collection', $collection->load(['string' => ['foo' => fn ($q) => $q]])); assertType('Illuminate\Database\Eloquent\Collection', $collection->load(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string'], 'string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string' => ['foo' => fn ($q) => $q]], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string'], 'string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAggregate(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); @@ -23,52 +25,61 @@ assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount(['string'])); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount(['string' => ['foo' => fn ($q) => $q]])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadCount(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax(['string'], 'string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax(['string' => ['foo' => fn ($q) => $q]], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMax(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin(['string'], 'string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin(['string' => ['foo' => fn ($q) => $q]], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMin(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum(['string'], 'string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum(['string' => ['foo' => fn ($q) => $q]], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadSum(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg('string', 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg(['string'], 'string')); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg(['string' => ['foo' => fn ($q) => $q]], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadAvg(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }], 'string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists(['string'])); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists(['string' => ['foo' => fn ($q) => $q]])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadExists(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing('string')); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing(['string'])); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing(['string' => ['foo' => fn ($q) => $q]])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMissing(['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorph('string', ['string'])); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorph('string', ['string' => ['foo' => fn ($q) => $q]])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorph('string', ['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorphCount('string', ['string'])); +assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorphCount('string', ['string' => ['foo' => fn ($q) => $q]])); assertType('Illuminate\Database\Eloquent\Collection', $collection->loadMorphCount('string', ['string' => function ($query) { // assertType('Illuminate\Database\Eloquent\Relations\Relation<*,*,*>', $query); }])); From 09bc55a32e4d017806e2969b98b02c7becb02e39 Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Thu, 12 Sep 2024 23:07:55 +1000 Subject: [PATCH 060/100] Update facade documenter (#52750) --- .github/workflows/facades.yml | 3 +++ src/Illuminate/Support/Facades/Concurrency.php | 15 +++++++++++++++ src/Illuminate/Support/Facades/Exceptions.php | 6 +++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/.github/workflows/facades.yml b/.github/workflows/facades.yml index c769e7ae22f8..a992ed5164f6 100644 --- a/.github/workflows/facades.yml +++ b/.github/workflows/facades.yml @@ -50,6 +50,7 @@ jobs: Illuminate\\Support\\Facades\\Broadcast \ Illuminate\\Support\\Facades\\Bus \ Illuminate\\Support\\Facades\\Cache \ + Illuminate\\Support\\Facades\\Concurrency \ Illuminate\\Support\\Facades\\Config \ Illuminate\\Support\\Facades\\Context \ Illuminate\\Support\\Facades\\Cookie \ @@ -57,6 +58,7 @@ jobs: Illuminate\\Support\\Facades\\DB \ Illuminate\\Support\\Facades\\Date \ Illuminate\\Support\\Facades\\Event \ + Illuminate\\Support\\Facades\\Exceptions \ Illuminate\\Support\\Facades\\File \ Illuminate\\Support\\Facades\\Gate \ Illuminate\\Support\\Facades\\Hash \ @@ -76,6 +78,7 @@ jobs: Illuminate\\Support\\Facades\\Request \ Illuminate\\Support\\Facades\\Response \ Illuminate\\Support\\Facades\\Route \ + Illuminate\\Support\\Facades\\Schedule \ Illuminate\\Support\\Facades\\Schema \ Illuminate\\Support\\Facades\\Session \ Illuminate\\Support\\Facades\\Storage \ diff --git a/src/Illuminate/Support/Facades/Concurrency.php b/src/Illuminate/Support/Facades/Concurrency.php index 4a9eab3a0d8d..a5e43be65850 100644 --- a/src/Illuminate/Support/Facades/Concurrency.php +++ b/src/Illuminate/Support/Facades/Concurrency.php @@ -5,6 +5,21 @@ use Illuminate\Concurrency\ConcurrencyManager; /** + * @method static mixed driver(string|null $name = null) + * @method static \Illuminate\Concurrency\ProcessDriver createProcessDriver(array $config) + * @method static \Illuminate\Concurrency\ForkDriver createForkDriver(array $config) + * @method static \Illuminate\Concurrency\SyncDriver createSyncDriver(array $config) + * @method static string getDefaultInstance() + * @method static void setDefaultInstance(string $name) + * @method static array getInstanceConfig(string $name) + * @method static mixed instance(string|null $name = null) + * @method static \Illuminate\Concurrency\ConcurrencyManager forgetInstance(array|string|null $name = null) + * @method static void purge(string|null $name = null) + * @method static \Illuminate\Concurrency\ConcurrencyManager extend(string $name, \Closure $callback) + * @method static \Illuminate\Concurrency\ConcurrencyManager setApplication(\Illuminate\Contracts\Foundation\Application $app) + * @method static array run(\Closure|array $tasks) + * @method static void background(\Closure|array $tasks) + * * @see \Illuminate\Concurrency\ConcurrencyManager */ class Concurrency extends Facade diff --git a/src/Illuminate/Support/Facades/Exceptions.php b/src/Illuminate/Support/Facades/Exceptions.php index d877d2742823..42015c0a6e9e 100644 --- a/src/Illuminate/Support/Facades/Exceptions.php +++ b/src/Illuminate/Support/Facades/Exceptions.php @@ -14,7 +14,7 @@ * @method static \Illuminate\Foundation\Exceptions\Handler dontReport(array|string $exceptions) * @method static \Illuminate\Foundation\Exceptions\Handler ignore(array|string $exceptions) * @method static \Illuminate\Foundation\Exceptions\Handler dontFlash(array|string $attributes) - * @method static \Illuminate\Foundation\Exceptions\Handler level(string $type, void $level) + * @method static \Illuminate\Foundation\Exceptions\Handler level(string $type, string $level) * @method static void report(\Throwable $e) * @method static bool shouldReport(\Throwable $e) * @method static \Illuminate\Foundation\Exceptions\Handler throttleUsing(callable $throttleUsing) @@ -25,16 +25,16 @@ * @method static \Illuminate\Foundation\Exceptions\Handler shouldRenderJsonWhen(callable $callback) * @method static \Illuminate\Foundation\Exceptions\Handler dontReportDuplicates() * @method static \Illuminate\Contracts\Debug\ExceptionHandler handler() - * @method static void assertNothingReported() * @method static void assertReported(\Closure|string $exception) * @method static void assertReportedCount(int $count) * @method static void assertNotReported(\Closure|string $exception) + * @method static void assertNothingReported() * @method static void renderForConsole(\Symfony\Component\Console\Output\OutputInterface $output, \Throwable $e) + * @method static \Illuminate\Support\Testing\Fakes\ExceptionHandlerFake throwOnReport() * @method static \Illuminate\Support\Testing\Fakes\ExceptionHandlerFake throwFirstReported() * @method static \Illuminate\Support\Testing\Fakes\ExceptionHandlerFake setHandler(\Illuminate\Contracts\Debug\ExceptionHandler $handler) * * @see \Illuminate\Foundation\Exceptions\Handler - * @see \Illuminate\Contracts\Debug\ExceptionHandler * @see \Illuminate\Support\Testing\Fakes\ExceptionHandlerFake */ class Exceptions extends Facade From 1f4d69dded2c242cd0cb3a20da9e3e9fd8e5a4c8 Mon Sep 17 00:00:00 2001 From: Lukas Heller Date: Thu, 12 Sep 2024 15:08:55 +0200 Subject: [PATCH 061/100] fix action button align prop (#52758) --- src/Illuminate/Mail/resources/views/html/themes/default.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Mail/resources/views/html/themes/default.css b/src/Illuminate/Mail/resources/views/html/themes/default.css index 2483b11685a3..09e31d8787f5 100644 --- a/src/Illuminate/Mail/resources/views/html/themes/default.css +++ b/src/Illuminate/Mail/resources/views/html/themes/default.css @@ -219,6 +219,7 @@ img { padding: 0; text-align: center; width: 100%; + float: unset; } .button { From b67f1ab2be64d9b3e9ceee6f877447fca2fe6fe3 Mon Sep 17 00:00:00 2001 From: AJ <60591772+devajmeireles@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:34:52 -0300 Subject: [PATCH 062/100] removing inexistent background and adding defer (#52764) --- src/Illuminate/Support/Facades/Concurrency.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Concurrency.php b/src/Illuminate/Support/Facades/Concurrency.php index a5e43be65850..b02e83203fba 100644 --- a/src/Illuminate/Support/Facades/Concurrency.php +++ b/src/Illuminate/Support/Facades/Concurrency.php @@ -18,7 +18,7 @@ * @method static \Illuminate\Concurrency\ConcurrencyManager extend(string $name, \Closure $callback) * @method static \Illuminate\Concurrency\ConcurrencyManager setApplication(\Illuminate\Contracts\Foundation\Application $app) * @method static array run(\Closure|array $tasks) - * @method static void background(\Closure|array $tasks) + * @method static \Illuminate\Foundation\Defer\DeferredCallback defer(\Closure|array $tasks) * * @see \Illuminate\Concurrency\ConcurrencyManager */ From def4668855dc4bc59dc90eaed3ee205102122354 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Thu, 12 Sep 2024 14:35:20 +0000 Subject: [PATCH 063/100] Update facade docblocks --- src/Illuminate/Support/Facades/Concurrency.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Concurrency.php b/src/Illuminate/Support/Facades/Concurrency.php index b02e83203fba..a5e43be65850 100644 --- a/src/Illuminate/Support/Facades/Concurrency.php +++ b/src/Illuminate/Support/Facades/Concurrency.php @@ -18,7 +18,7 @@ * @method static \Illuminate\Concurrency\ConcurrencyManager extend(string $name, \Closure $callback) * @method static \Illuminate\Concurrency\ConcurrencyManager setApplication(\Illuminate\Contracts\Foundation\Application $app) * @method static array run(\Closure|array $tasks) - * @method static \Illuminate\Foundation\Defer\DeferredCallback defer(\Closure|array $tasks) + * @method static void background(\Closure|array $tasks) * * @see \Illuminate\Concurrency\ConcurrencyManager */ From 982710a9a5fb10be154694166746b3cd7b3ad386 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Thu, 12 Sep 2024 11:50:04 -0300 Subject: [PATCH 064/100] [11.x] add lazy default to when helper (#52747) * add lazy default to when helper * Update helpers.php --------- Co-authored-by: Taylor Otwell --- src/Illuminate/Collections/helpers.php | 11 ++++++----- tests/Support/SupportHelpersTest.php | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/Illuminate/Collections/helpers.php b/src/Illuminate/Collections/helpers.php index 58dcecfb1452..ef9c45e2a79b 100644 --- a/src/Illuminate/Collections/helpers.php +++ b/src/Illuminate/Collections/helpers.php @@ -239,18 +239,19 @@ function value($value, ...$args) if (! function_exists('when')) { /** - * Output a value if the given condition is true. + * Return a value if the given condition is true. * * @param mixed $condition - * @param \Closure|mixed $output + * @param \Closure|mixed $value + * @param \Closure|mixed $default * @return mixed */ - function when($condition, $output) + function when($condition, $value, $default = null) { if ($condition) { - return value($output); + return value($value, $condition); } - return null; + return value($default, $condition); } } diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 69877bfb3a2a..f750ab4518c9 100644 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -103,15 +103,23 @@ public function testClassBasename() public function testWhen() { $this->assertEquals('Hello', when(true, 'Hello')); - $this->assertEquals(null, when(false, 'Hello')); + $this->assertNull(when(false, 'Hello')); $this->assertEquals('There', when(1 === 1, 'There')); // strict types $this->assertEquals('There', when(1 == '1', 'There')); // loose types - $this->assertEquals(null, when(1 == 2, 'There')); - $this->assertEquals(null, when('1', fn () => null)); - $this->assertEquals(null, when(0, fn () => null)); + $this->assertNull(when(1 == 2, 'There')); + $this->assertNull(when('1', fn () => null)); + $this->assertNull(when(0, fn () => null)); $this->assertEquals('True', when([1, 2, 3, 4], 'True')); // Array - $this->assertEquals(null, when([], 'True')); // Empty Array = Falsy + $this->assertNull(when([], 'True')); // Empty Array = Falsy $this->assertEquals('True', when(new StdClass, fn () => 'True')); // Object + $this->assertEquals('World', when(false, 'Hello', 'World')); + $this->assertEquals('World', when(1 === 0, 'Hello', 'World')); // strict types + $this->assertEquals('World', when(1 == '0', 'Hello', 'World')); // loose types + $this->assertNull(when('', fn () => 'There', fn () => null)); + $this->assertNull(when(0, fn () => 'There', fn () => null)); + $this->assertEquals('False', when([], 'True', 'False')); // Empty Array = Falsy + $this->assertTrue(when(true, fn ($value) => $value, fn ($value) => ! $value)); // lazy evaluation + $this->assertTrue(when(false, fn ($value) => $value, fn ($value) => ! $value)); // lazy evaluation } public function testFilled() From 58c20532011fd4db5b5d6e62dd1d668b89ed2daa Mon Sep 17 00:00:00 2001 From: Will Rowe Date: Thu, 12 Sep 2024 10:51:40 -0400 Subject: [PATCH 065/100] Fix arguments passed to artisan commands that start with 'env' (#52748) * Add failing tests * Ensure that there is an equal sign so that argument names starting with 'env' are not matched --- .../Foundation/EnvironmentDetector.php | 2 +- .../FoundationEnvironmentDetectorTest.php | 30 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/EnvironmentDetector.php b/src/Illuminate/Foundation/EnvironmentDetector.php index b2747bc6800e..8fa61bd2e983 100644 --- a/src/Illuminate/Foundation/EnvironmentDetector.php +++ b/src/Illuminate/Foundation/EnvironmentDetector.php @@ -65,7 +65,7 @@ protected function getEnvironmentArgument(array $args) return $args[$i + 1] ?? null; } - if (str_starts_with($value, '--env')) { + if (str_starts_with($value, '--env=')) { return head(array_slice(explode('=', $value), 1)); } } diff --git a/tests/Foundation/FoundationEnvironmentDetectorTest.php b/tests/Foundation/FoundationEnvironmentDetectorTest.php index d302c375bf50..c06a8ac386dd 100644 --- a/tests/Foundation/FoundationEnvironmentDetectorTest.php +++ b/tests/Foundation/FoundationEnvironmentDetectorTest.php @@ -46,4 +46,34 @@ public function testConsoleEnvironmentDetectionWithNoValue() }, ['--env']); $this->assertSame('foobar', $result); } + + public function testConsoleEnvironmentDetectionDoesNotUseArgumentThatStartsWithEnv() + { + $env = new EnvironmentDetector; + + $result = $env->detect(function () { + return 'foobar'; + }, ['--envelope=mail']); + $this->assertSame('foobar', $result); + } + + public function testConsoleEnvironmentDetectionDoesNotUseArgumentThatStartsWithEnvSeparatedWithSpace() + { + $env = new EnvironmentDetector; + + $result = $env->detect(function () { + return 'foobar'; + }, ['--envelope', 'mail']); + $this->assertSame('foobar', $result); + } + + public function testConsoleEnvironmentDetectionDoesNotUseArgumentThatStartsWithEnvWithNoValue() + { + $env = new EnvironmentDetector; + + $result = $env->detect(function () { + return 'foobar'; + }, ['--envelope']); + $this->assertSame('foobar', $result); + } } From 32ee0f232006d39e0432ef52c89e7cdb698ec0fa Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 12 Sep 2024 09:56:42 -0500 Subject: [PATCH 066/100] wip --- src/Illuminate/Foundation/Application.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Application.php b/src/Illuminate/Foundation/Application.php index dbd862f6cbc0..1fe168b98c7d 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.23.2'; + const VERSION = '11.23.3'; /** * The base path for the Laravel installation. From 08b46ae7be52f71688196f98556d21cb2ec799b6 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Thu, 12 Sep 2024 14:57:48 +0000 Subject: [PATCH 067/100] Update version to v11.23.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 1fe168b98c7d..1ca9d220f023 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.23.3'; + const VERSION = '11.23.4'; /** * The base path for the Laravel installation. From 73f36e14ec4372cb7b9a62c62042640f5843cb74 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Thu, 12 Sep 2024 14:59:25 +0000 Subject: [PATCH 068/100] Update CHANGELOG --- CHANGELOG.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79b3bc6044d4..ae9ba0adae65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.23.2...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.23.4...11.x) + +## [v11.23.4](https://github.com/laravel/framework/compare/v11.23.2...v11.23.4) - 2024-09-12 + +* [10.x] Fixes `whereDate`, `whereDay`, `whereMonth`, `whereTime`, `whereYear` and `whereJsonLength` to ignore invalid `$operator` by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52704 +* [11.x] Fixing Concurrency Facade Docblocks by [@devajmeireles](https://github.com/devajmeireles) in https://github.com/laravel/framework/pull/52764 +* [11.x] add lazy default to when helper by [@rodrigopedra](https://github.com/rodrigopedra) in https://github.com/laravel/framework/pull/52747 +* Fix arguments passed to artisan commands that start with 'env' by [@willrowe](https://github.com/willrowe) in https://github.com/laravel/framework/pull/52748 ## [v11.23.2](https://github.com/laravel/framework/compare/v11.23.1...v11.23.2) - 2024-09-11 From ab41b23feefdca5764fc18b405994e0ae5a2de8e Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Thu, 12 Sep 2024 10:25:08 -0500 Subject: [PATCH 069/100] fix drivers --- src/Illuminate/Concurrency/ForkDriver.php | 3 ++- src/Illuminate/Concurrency/ProcessDriver.php | 3 ++- src/Illuminate/Concurrency/SyncDriver.php | 3 ++- src/Illuminate/Contracts/Concurrency/Driver.php | 5 +++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Concurrency/ForkDriver.php b/src/Illuminate/Concurrency/ForkDriver.php index beca07d7e44b..a02ee54a94a8 100644 --- a/src/Illuminate/Concurrency/ForkDriver.php +++ b/src/Illuminate/Concurrency/ForkDriver.php @@ -3,11 +3,12 @@ namespace Illuminate\Concurrency; use Closure; +use Illuminate\Contracts\Concurrency\Driver; use Illuminate\Foundation\Defer\DeferredCallback; use Illuminate\Support\Arr; use Spatie\Fork\Fork; -class ForkDriver +class ForkDriver implements Driver { /** * Run the given tasks concurrently and return an array containing the results. diff --git a/src/Illuminate/Concurrency/ProcessDriver.php b/src/Illuminate/Concurrency/ProcessDriver.php index 19dd392fa41e..6c6300c99f32 100644 --- a/src/Illuminate/Concurrency/ProcessDriver.php +++ b/src/Illuminate/Concurrency/ProcessDriver.php @@ -3,6 +3,7 @@ namespace Illuminate\Concurrency; use Closure; +use Illuminate\Contracts\Concurrency\Driver; use Illuminate\Foundation\Defer\DeferredCallback; use Illuminate\Process\Factory as ProcessFactory; use Illuminate\Process\Pool; @@ -10,7 +11,7 @@ use Laravel\SerializableClosure\SerializableClosure; use Symfony\Component\Process\PhpExecutableFinder; -class ProcessDriver +class ProcessDriver implements Driver { /** * Create a new process based concurrency driver. diff --git a/src/Illuminate/Concurrency/SyncDriver.php b/src/Illuminate/Concurrency/SyncDriver.php index 10580124f3bd..62bb552ae664 100644 --- a/src/Illuminate/Concurrency/SyncDriver.php +++ b/src/Illuminate/Concurrency/SyncDriver.php @@ -3,10 +3,11 @@ namespace Illuminate\Concurrency; use Closure; +use Illuminate\Contracts\Concurrency\Driver; use Illuminate\Foundation\Defer\DeferredCallback; use Illuminate\Support\Arr; -class SyncDriver +class SyncDriver implements Driver { /** * Run the given tasks concurrently and return an array containing the results. diff --git a/src/Illuminate/Contracts/Concurrency/Driver.php b/src/Illuminate/Contracts/Concurrency/Driver.php index ce8f2ae0d900..fd963ea6c672 100644 --- a/src/Illuminate/Contracts/Concurrency/Driver.php +++ b/src/Illuminate/Contracts/Concurrency/Driver.php @@ -3,6 +3,7 @@ namespace Illuminate\Contracts\Concurrency; use Closure; +use Illuminate\Foundation\Defer\DeferredCallback; interface Driver { @@ -12,7 +13,7 @@ interface Driver public function run(Closure|array $tasks): array; /** - * Start the given tasks in the background. + * Defer the execution of the given tasks. */ - public function background(Closure|array $tasks): void; + public function defer(Closure|array $tasks): DeferredCallback; } From 2f3a005631135cf1b47bfea613b1b1c7b5586996 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Thu, 12 Sep 2024 15:25:39 +0000 Subject: [PATCH 070/100] Update facade docblocks --- src/Illuminate/Support/Facades/Concurrency.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Support/Facades/Concurrency.php b/src/Illuminate/Support/Facades/Concurrency.php index a5e43be65850..b02e83203fba 100644 --- a/src/Illuminate/Support/Facades/Concurrency.php +++ b/src/Illuminate/Support/Facades/Concurrency.php @@ -18,7 +18,7 @@ * @method static \Illuminate\Concurrency\ConcurrencyManager extend(string $name, \Closure $callback) * @method static \Illuminate\Concurrency\ConcurrencyManager setApplication(\Illuminate\Contracts\Foundation\Application $app) * @method static array run(\Closure|array $tasks) - * @method static void background(\Closure|array $tasks) + * @method static \Illuminate\Foundation\Defer\DeferredCallback defer(\Closure|array $tasks) * * @see \Illuminate\Concurrency\ConcurrencyManager */ From 54cf9db8a57744f4c6f755a0bedc222a4d9cd664 Mon Sep 17 00:00:00 2001 From: Miguel Piedrafita Date: Thu, 12 Sep 2024 20:04:04 +0200 Subject: [PATCH 071/100] allow recursive Model::withoutTimestamps calls (#52768) * allow recursive Model::withoutTimestamps calls * cs * cs --- src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php | 6 +++++- tests/Database/DatabaseEloquentTimestampsTest.php | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php b/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php index 2b6dfab6548e..47044238df16 100644 --- a/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php +++ b/src/Illuminate/Database/Eloquent/Concerns/HasTimestamps.php @@ -199,7 +199,11 @@ public static function withoutTimestampsOn($models, $callback) try { return $callback(); } finally { - static::$ignoreTimestampsOn = array_values(array_diff(static::$ignoreTimestampsOn, $models)); + foreach ($models as $model) { + if (($key = array_search($model, static::$ignoreTimestampsOn, true)) !== false) { + unset(static::$ignoreTimestampsOn[$key]); + } + } } } diff --git a/tests/Database/DatabaseEloquentTimestampsTest.php b/tests/Database/DatabaseEloquentTimestampsTest.php index aa12f22f0266..9bc8d59b86a4 100644 --- a/tests/Database/DatabaseEloquentTimestampsTest.php +++ b/tests/Database/DatabaseEloquentTimestampsTest.php @@ -112,6 +112,12 @@ public function testWithoutTimestamp() $this->assertTrue($user->usesTimestamps()); $user->withoutTimestamps(function () use ($user) { + $this->assertFalse($user->usesTimestamps()); + + $user->withoutTimestamps(function () use ($user) { + $this->assertFalse($user->usesTimestamps()); + }); + $this->assertFalse($user->usesTimestamps()); $user->update([ 'email' => 'bar@example.com', From a9a080f51fe3ef6a5b5db9d50ca4e2da34d966cf Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 13 Sep 2024 21:34:17 +0800 Subject: [PATCH 072/100] [11.x] Fixes out of memory issue running `route:cache` with ServeFile (#52781) fixes #52779 Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Filesystem/FilesystemServiceProvider.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Filesystem/FilesystemServiceProvider.php b/src/Illuminate/Filesystem/FilesystemServiceProvider.php index 28fe5f60b23a..5fe6b9e531be 100644 --- a/src/Illuminate/Filesystem/FilesystemServiceProvider.php +++ b/src/Illuminate/Filesystem/FilesystemServiceProvider.php @@ -88,16 +88,18 @@ protected function serveFiles() continue; } - $this->app->booted(function () use ($disk, $config) { + $this->app->booted(function ($app) use ($disk, $config) { $uri = isset($config['url']) ? rtrim(parse_url($config['url'])['path'], '/') : '/storage'; - Route::get($uri.'/{path}', function (Request $request, string $path) use ($disk, $config) { + $isProduction = $app->isProduction(); + + Route::get($uri.'/{path}', function (Request $request, string $path) use ($disk, $config, $isProduction) { return (new ServeFile( $disk, $config, - $this->app->isProduction() + $isProduction ))($request, $path); })->where('path', '.*')->name('storage.'.$disk); }); From 16b31ab0e1dad5cb2ed6dcc1818c02f02fc48453 Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Fri, 13 Sep 2024 13:36:30 +0000 Subject: [PATCH 073/100] Update version to v11.23.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 1ca9d220f023..41653ec0c8c1 100755 --- a/src/Illuminate/Foundation/Application.php +++ b/src/Illuminate/Foundation/Application.php @@ -45,7 +45,7 @@ class Application extends Container implements ApplicationContract, CachesConfig * * @var string */ - const VERSION = '11.23.4'; + const VERSION = '11.23.5'; /** * The base path for the Laravel installation. From e2196b182a375fb6e7ecdaf8ad80fd708a75326c Mon Sep 17 00:00:00 2001 From: taylorotwell Date: Fri, 13 Sep 2024 13:38:15 +0000 Subject: [PATCH 074/100] Update CHANGELOG --- CHANGELOG.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae9ba0adae65..1523f031c3e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Release Notes for 11.x -## [Unreleased](https://github.com/laravel/framework/compare/v11.23.4...11.x) +## [Unreleased](https://github.com/laravel/framework/compare/v11.23.5...11.x) + +## [v11.23.5](https://github.com/laravel/framework/compare/v11.23.4...v11.23.5) - 2024-09-13 + +* allow recursive Model::withoutTimestamps calls by [@m1guelpf](https://github.com/m1guelpf) in https://github.com/laravel/framework/pull/52768 +* [11.x] Fixes out of memory issue running `route:cache` with ServeFile by [@crynobone](https://github.com/crynobone) in https://github.com/laravel/framework/pull/52781 ## [v11.23.4](https://github.com/laravel/framework/compare/v11.23.2...v11.23.4) - 2024-09-12 From da28aabb597ec4ea6f43bcaf82e263a0fad8737f Mon Sep 17 00:00:00 2001 From: Roj Vroemen Date: Fri, 13 Sep 2024 18:10:44 +0200 Subject: [PATCH 075/100] [11.x] Fix issue where `$name` variable in non base config file becomes it's key (#52738) * Prepare example where the `$name` variable becomes the key of the config * Require path in isolation * Use arrow function --- .../Foundation/Bootstrap/LoadConfiguration.php | 2 +- tests/Foundation/Bootstrap/LoadConfigurationTest.php | 11 +++++++++++ tests/Foundation/fixtures/config/custom.php | 7 +++++++ 3 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/Foundation/fixtures/config/custom.php diff --git a/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php b/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php index a1bc7460aa36..2fa429f83034 100644 --- a/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php +++ b/src/Illuminate/Foundation/Bootstrap/LoadConfiguration.php @@ -93,7 +93,7 @@ protected function loadConfigurationFiles(Application $app, RepositoryContract $ */ protected function loadConfigurationFile(RepositoryContract $repository, $name, $path, array $base) { - $config = require $path; + $config = (fn () => require $path)(); if (isset($base[$name])) { $config = array_merge($base[$name], $config); diff --git a/tests/Foundation/Bootstrap/LoadConfigurationTest.php b/tests/Foundation/Bootstrap/LoadConfigurationTest.php index 3ab29da410c6..f8d3f387c1d2 100644 --- a/tests/Foundation/Bootstrap/LoadConfigurationTest.php +++ b/tests/Foundation/Bootstrap/LoadConfigurationTest.php @@ -26,4 +26,15 @@ public function testDontLoadBaseConfiguration() $this->assertNull($app['config']['app.name']); } + + public function testLoadsConfigurationInIsolation() + { + $app = new Application(__DIR__.'/../fixtures'); + $app->useConfigPath(__DIR__.'/../fixtures/config'); + + (new LoadConfiguration())->bootstrap($app); + + $this->assertNull($app['config']['bar.foo']); + $this->assertSame('bar', $app['config']['custom.foo']); + } } diff --git a/tests/Foundation/fixtures/config/custom.php b/tests/Foundation/fixtures/config/custom.php new file mode 100644 index 000000000000..e07153f0cae1 --- /dev/null +++ b/tests/Foundation/fixtures/config/custom.php @@ -0,0 +1,7 @@ + $name, +]; From 05d920410e58f04b13dcaa83937b9d4574fc9b3f Mon Sep 17 00:00:00 2001 From: Amit Merchant Date: Mon, 16 Sep 2024 06:36:29 +0530 Subject: [PATCH 076/100] =?UTF-8?q?teh=20=E2=86=92=20the=20(#52797)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php b/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php index 55749481da59..aa6907d0fb2a 100644 --- a/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php +++ b/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php @@ -115,7 +115,7 @@ public function offsetGet(mixed $offset): mixed } /** - * Set teh callback with the given key. + * Set the callback with the given key. * * @param mixed $offset * @param mixed $value From 25affbbc07856509359beb715f3c7fcdb3c6df5d Mon Sep 17 00:00:00 2001 From: Quynh Nguyen Date: Mon, 16 Sep 2024 21:12:53 +0700 Subject: [PATCH 077/100] Remove a unused import and fix docblock for DeferredCallbackCollection (#52808) --- src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php b/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php index aa6907d0fb2a..4c072bc8f2ba 100644 --- a/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php +++ b/src/Illuminate/Foundation/Defer/DeferredCallbackCollection.php @@ -5,7 +5,6 @@ use ArrayAccess; use Closure; use Countable; -use Illuminate\Support\Collection; class DeferredCallbackCollection implements ArrayAccess, Countable { @@ -39,7 +38,7 @@ public function invoke(): void /** * Invoke the deferred callbacks if the given truth test evaluates to true. * - * @param \Closure $when + * @param \Closure|null $when * @return void */ public function invokeWhen(?Closure $when = null): void From 6dd3a51c41580418579c0bee62aebf64925d9a95 Mon Sep 17 00:00:00 2001 From: webartisan10 Date: Mon, 16 Sep 2024 19:18:07 +0500 Subject: [PATCH 078/100] Update DetectsLostConnections trait (#52805) Co-authored-by: Daniyar Islamgaliyev --- 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 dc987adb4118..237ffaab9e98 100644 --- a/src/Illuminate/Database/DetectsLostConnections.php +++ b/src/Illuminate/Database/DetectsLostConnections.php @@ -52,7 +52,6 @@ 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', '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', @@ -71,6 +70,7 @@ protected function causedByLostConnection(Throwable $e) 'SQLSTATE[HY000] [2002] Network is unreachable', '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] [2002] Operation now in progress', 'SQLSTATE[HY000]: General error: 3989', 'went away', 'No such file or directory', From 6143186f3e3b7dbdece6ccb0823c09495aec4492 Mon Sep 17 00:00:00 2001 From: Noboru Shiroiwa <14008307+nshiro@users.noreply.github.com> Date: Mon, 16 Sep 2024 23:20:17 +0900 Subject: [PATCH 079/100] [11.x] Add prependLocation method to View Factory (#52806) --- src/Illuminate/Support/Facades/View.php | 1 + src/Illuminate/View/Factory.php | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/src/Illuminate/Support/Facades/View.php b/src/Illuminate/Support/Facades/View.php index 10eaa645d548..90eb67bb66b9 100755 --- a/src/Illuminate/Support/Facades/View.php +++ b/src/Illuminate/Support/Facades/View.php @@ -18,6 +18,7 @@ * @method static bool hasRenderedOnce(string $id) * @method static void markAsRenderedOnce(string $id) * @method static void addLocation(string $location) + * @method static void prependLocation(string $location) * @method static \Illuminate\View\Factory addNamespace(string $namespace, string|array $hints) * @method static \Illuminate\View\Factory prependNamespace(string $namespace, string|array $hints) * @method static \Illuminate\View\Factory replaceNamespace(string $namespace, string|array $hints) diff --git a/src/Illuminate/View/Factory.php b/src/Illuminate/View/Factory.php index bc6e59d55afd..e5efe067e86e 100755 --- a/src/Illuminate/View/Factory.php +++ b/src/Illuminate/View/Factory.php @@ -424,6 +424,17 @@ public function addLocation($location) $this->finder->addLocation($location); } + /** + * Prepend a location to the array of view locations. + * + * @param string $location + * @return void + */ + public function prependLocation($location) + { + $this->finder->prependLocation($location); + } + /** * Add a new namespace to the loader. * From 0890706eecfec112ab850919a74d83b3e19f49f6 Mon Sep 17 00:00:00 2001 From: gisu nasrollahi <113020788+gisuNasr@users.noreply.github.com> Date: Mon, 16 Sep 2024 17:55:30 +0330 Subject: [PATCH 080/100] [11.x] add nullOnUpdate() method to ForeignKeyDefinition (#52798) * add nullOnUpdate() method to ForeignKeyDefinition * Update ForeignKeyDefinition.php --------- Co-authored-by: Taylor Otwell --- .../Database/Schema/ForeignKeyDefinition.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Illuminate/Database/Schema/ForeignKeyDefinition.php b/src/Illuminate/Database/Schema/ForeignKeyDefinition.php index 6682da30c81b..1ce0361b9377 100644 --- a/src/Illuminate/Database/Schema/ForeignKeyDefinition.php +++ b/src/Illuminate/Database/Schema/ForeignKeyDefinition.php @@ -34,6 +34,16 @@ public function restrictOnUpdate() return $this->onUpdate('restrict'); } + /** + * Indicate that updates should set the foreign key value to null. + * + * @return $this + */ + public function nullOnUpdate() + { + return $this->onUpdate('set null'); + } + /** * Indicate that updates should have "no action". * From 56d6194c8e9043837f586a6a0b0e9e07fc1caf33 Mon Sep 17 00:00:00 2001 From: Omegadela Date: Mon, 16 Sep 2024 16:31:38 +0200 Subject: [PATCH 081/100] [11.x] Allow `BackedEnum` to be passed to `Route::can()` (#52792) * Add backed enum support to can * Add RouteCanBackedEnumTest and related enum file --- src/Illuminate/Routing/Route.php | 4 +- .../Integration/Routing/AbilityBackedEnum.php | 9 +++++ .../Routing/RouteCanBackedEnumTest.php | 40 +++++++++++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/Integration/Routing/AbilityBackedEnum.php create mode 100644 tests/Integration/Routing/RouteCanBackedEnumTest.php diff --git a/src/Illuminate/Routing/Route.php b/src/Illuminate/Routing/Route.php index b114d22203d6..2f44fa711396 100755 --- a/src/Illuminate/Routing/Route.php +++ b/src/Illuminate/Routing/Route.php @@ -1081,12 +1081,14 @@ public function middleware($middleware = null) /** * Specify that the "Authorize" / "can" middleware should be applied to the route with the given options. * - * @param string $ability + * @param \BackedEnum|string $ability * @param array|string $models * @return $this */ public function can($ability, $models = []) { + $ability = $ability instanceof BackedEnum ? $ability->value : $ability; + return empty($models) ? $this->middleware(['can:'.$ability]) : $this->middleware(['can:'.$ability.','.implode(',', Arr::wrap($models))]); diff --git a/tests/Integration/Routing/AbilityBackedEnum.php b/tests/Integration/Routing/AbilityBackedEnum.php new file mode 100644 index 000000000000..129b0b303709 --- /dev/null +++ b/tests/Integration/Routing/AbilityBackedEnum.php @@ -0,0 +1,9 @@ + false); + $this->assertArrayHasKey('not-access-route', $gate->abilities()); + + $route = Route::get('/', function () { + return 'Hello World'; + })->can(AbilityBackedEnum::NotAccessRoute); + $this->assertEquals(['can:not-access-route'], $route->middleware()); + + $response = $this->get('/'); + $response->assertForbidden(); + } + + public function testSimpleRouteWithStringBackedEnumCanAbilityGuestAllowedThroughTheFramework() + { + $gate = Gate::define(AbilityBackedEnum::AccessRoute, fn (?User $user) => true); + $this->assertArrayHasKey('access-route', $gate->abilities()); + + $route = Route::get('/', function () { + return 'Hello World'; + })->can(AbilityBackedEnum::AccessRoute); + $this->assertEquals(['can:access-route'], $route->middleware()); + + $response = $this->get('/'); + $response->assertOk(); + $response->assertContent('Hello World'); + } +} From 1c52ec4052e150efcbf969597032abf4ee4e7bec Mon Sep 17 00:00:00 2001 From: Tim MacDonald Date: Tue, 17 Sep 2024 00:31:56 +1000 Subject: [PATCH 082/100] Ensure headers are only attached to illuminate responses (#52789) --- .../AddLinkHeadersForPreloadedAssets.php | 3 ++- tests/Http/Middleware/VitePreloadingTest.php | 26 +++++++++++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php index 93ca06e958b5..36ffb7b3e480 100644 --- a/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php +++ b/src/Illuminate/Http/Middleware/AddLinkHeadersForPreloadedAssets.php @@ -2,6 +2,7 @@ namespace Illuminate\Http\Middleware; +use Illuminate\Http\Response; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Vite; @@ -17,7 +18,7 @@ class AddLinkHeadersForPreloadedAssets public function handle($request, $next) { return tap($next($request), function ($response) { - if (Vite::preloadedAssets() !== []) { + if ($response instanceof Response && Vite::preloadedAssets() !== []) { $response->header('Link', Collection::make(Vite::preloadedAssets()) ->map(fn ($attributes, $url) => "<{$url}>; ".implode('; ', $attributes)) ->join(', ')); diff --git a/tests/Http/Middleware/VitePreloadingTest.php b/tests/Http/Middleware/VitePreloadingTest.php index 641280cc8d9e..63b64b99519e 100644 --- a/tests/Http/Middleware/VitePreloadingTest.php +++ b/tests/Http/Middleware/VitePreloadingTest.php @@ -9,6 +9,7 @@ use Illuminate\Http\Response; use Illuminate\Support\Facades\Facade; use PHPUnit\Framework\TestCase; +use Symfony\Component\HttpFoundation\Response as SymfonyResponse; class VitePreloadingTest extends TestCase { @@ -20,7 +21,7 @@ protected function tearDown(): void public function testItDoesNotSetLinkTagWhenNoTagsHaveBeenPreloaded() { - $app = new Container(); + $app = new Container; $app->instance(Vite::class, new class extends Vite { protected $preloadedAssets = []; @@ -36,7 +37,7 @@ public function testItDoesNotSetLinkTagWhenNoTagsHaveBeenPreloaded() public function testItAddsPreloadLinkHeader() { - $app = new Container(); + $app = new Container; $app->instance(Vite::class, new class extends Vite { protected $preloadedAssets = [ @@ -57,4 +58,25 @@ public function testItAddsPreloadLinkHeader() '; rel="modulepreload"; foo="bar"' ); } + + public function testItDoesNotAttachHeadersToNonIlluminateResponses() + { + $app = new Container; + $app->instance(Vite::class, new class extends Vite + { + protected $preloadedAssets = [ + 'https://laravel.com/app.js' => [ + 'rel="modulepreload"', + 'foo="bar"', + ], + ]; + }); + Facade::setFacadeApplication($app); + + $response = (new AddLinkHeadersForPreloadedAssets)->handle(new Request, function () { + return new SymfonyResponse('Hello Laravel'); + }); + + $this->assertNull($response->headers->get('Link')); + } } From 4b47b4ec15dde8948c5275de2bfe75ce85efdc81 Mon Sep 17 00:00:00 2001 From: Caleb White Date: Mon, 16 Sep 2024 09:32:15 -0500 Subject: [PATCH 083/100] feat: improve groupBy, keyBy generics (#52787) --- phpstan.types.neon.dist | 2 ++ src/Illuminate/Collections/Collection.php | 12 +++++++---- src/Illuminate/Collections/Enumerable.php | 12 +++++++---- src/Illuminate/Collections/LazyCollection.php | 12 +++++++---- types/Support/Collection.php | 21 ++++++++++--------- types/Support/LazyCollection.php | 20 +++++++++--------- 6 files changed, 47 insertions(+), 32 deletions(-) diff --git a/phpstan.types.neon.dist b/phpstan.types.neon.dist index be73ed150cf8..ae92137be35f 100644 --- a/phpstan.types.neon.dist +++ b/phpstan.types.neon.dist @@ -2,3 +2,5 @@ parameters: level: max paths: - types + ignoreErrors: + - identifier: argument.templateType diff --git a/src/Illuminate/Collections/Collection.php b/src/Illuminate/Collections/Collection.php index fdfde565b89f..6aed4397da65 100644 --- a/src/Illuminate/Collections/Collection.php +++ b/src/Illuminate/Collections/Collection.php @@ -486,9 +486,11 @@ public function getOrPut($key, $value) /** * Group an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @template TGroupKey of array-key + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy * @param bool $preserveKeys - * @return static> + * @return static> */ public function groupBy($groupBy, $preserveKeys = false) { @@ -537,8 +539,10 @@ public function groupBy($groupBy, $preserveKeys = false) /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static */ public function keyBy($keyBy) { diff --git a/src/Illuminate/Collections/Enumerable.php b/src/Illuminate/Collections/Enumerable.php index 9bb29c9727b2..fcf9fe504ffc 100644 --- a/src/Illuminate/Collections/Enumerable.php +++ b/src/Illuminate/Collections/Enumerable.php @@ -518,17 +518,21 @@ public function get($key, $default = null); /** * Group an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @template TGroupKey of array-key + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy * @param bool $preserveKeys - * @return static> + * @return static> */ public function groupBy($groupBy, $preserveKeys = false); /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static */ public function keyBy($keyBy); diff --git a/src/Illuminate/Collections/LazyCollection.php b/src/Illuminate/Collections/LazyCollection.php index ab0534685fa7..d00da44f1d97 100644 --- a/src/Illuminate/Collections/LazyCollection.php +++ b/src/Illuminate/Collections/LazyCollection.php @@ -544,9 +544,11 @@ public function get($key, $default = null) /** * Group an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $groupBy + * @template TGroupKey of array-key + * + * @param (callable(TValue, TKey): TGroupKey)|array|string $groupBy * @param bool $preserveKeys - * @return static> + * @return static> */ public function groupBy($groupBy, $preserveKeys = false) { @@ -556,8 +558,10 @@ public function groupBy($groupBy, $preserveKeys = false) /** * Key an associative array by a field or using a callback. * - * @param (callable(TValue, TKey): array-key)|array|string $keyBy - * @return static + * @template TNewKey of array-key + * + * @param (callable(TValue, TKey): TNewKey)|array|string $keyBy + * @return static */ public function keyBy($keyBy) { diff --git a/types/Support/Collection.php b/types/Support/Collection.php index 290973923f17..8a0805ee3be4 100644 --- a/types/Support/Collection.php +++ b/types/Support/Collection.php @@ -514,22 +514,23 @@ function ($collection, $count) { assertType('Illuminate\Support\Collection', $collection::make(['string'])->flip()); -assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), User>>', $collection->groupBy('name')); -assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), User>>', $collection->groupBy('name', true)); -assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), User>>', $collection->groupBy(function ($user, $int) { - // assertType('User', $user); - // assertType('int', $int); +assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection>', $collection->groupBy('name')); +assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection>', $collection->groupBy('name', true)); +assertType('Illuminate\Support\Collection>', $collection->groupBy(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); return 'foo'; })); -assertType('Illuminate\Support\Collection<(int|string), Illuminate\Support\Collection<(int|string), User>>', $collection->groupBy(function ($user) { + +assertType('Illuminate\Support\Collection>', $collection->keyBy(fn () => '')->groupBy(function ($user) { return 'foo'; -})); +}, preserveKeys: true)); assertType('Illuminate\Support\Collection<(int|string), User>', $collection->keyBy('name')); -assertType('Illuminate\Support\Collection<(int|string), User>', $collection->keyBy(function ($user, $int) { - // assertType('User', $user); - // assertType('int', $int); +assertType('Illuminate\Support\Collection', $collection->keyBy(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); return 'foo'; })); diff --git a/types/Support/LazyCollection.php b/types/Support/LazyCollection.php index b3b8f73637b4..51694fdf4488 100644 --- a/types/Support/LazyCollection.php +++ b/types/Support/LazyCollection.php @@ -393,22 +393,22 @@ assertType('Illuminate\Support\LazyCollection', $collection::make(['string'])->flip()); -assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection<(int|string), User>>', $collection->groupBy('name')); -assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection<(int|string), User>>', $collection->groupBy('name', true)); -assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection<(int|string), User>>', $collection->groupBy(function ($user, $int) { - // assertType('User', $user); - // assertType('int', $int); +assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection>', $collection->groupBy('name')); +assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection>', $collection->groupBy('name', true)); +assertType('Illuminate\Support\LazyCollection>', $collection->groupBy(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); return 'foo'; })); -assertType('Illuminate\Support\LazyCollection<(int|string), Illuminate\Support\LazyCollection<(int|string), User>>', $collection->groupBy(function ($user) { +assertType('Illuminate\Support\LazyCollection>', $collection->keyBy(fn () => '')->groupBy(function ($user) { return 'foo'; -})); +}, true)); assertType('Illuminate\Support\LazyCollection<(int|string), User>', $collection->keyBy('name')); -assertType('Illuminate\Support\LazyCollection<(int|string), User>', $collection->keyBy(function ($user, $int) { - // assertType('User', $user); - // assertType('int', $int); +assertType('Illuminate\Support\LazyCollection', $collection->keyBy(function ($user, $int) { + assertType('User', $user); + assertType('int', $int); return 'foo'; })); From fbef34c77b257be14bf6d7668bd02dc7f19a3aa6 Mon Sep 17 00:00:00 2001 From: AJ <60591772+devajmeireles@users.noreply.github.com> Date: Mon, 16 Sep 2024 11:32:58 -0300 Subject: [PATCH 084/100] [Fix] Using correct concurrency configuration index name (#52788) --- src/Illuminate/Concurrency/ConcurrencyManager.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Concurrency/ConcurrencyManager.php b/src/Illuminate/Concurrency/ConcurrencyManager.php index 27646401bd21..b92272c0fb94 100644 --- a/src/Illuminate/Concurrency/ConcurrencyManager.php +++ b/src/Illuminate/Concurrency/ConcurrencyManager.php @@ -71,7 +71,7 @@ public function createSyncDriver(array $config) */ public function getDefaultInstance() { - return $this->app['config']['concurrency.default'] ?? 'process'; + return $this->app['config']['concurrency.driver'] ?? 'process'; } /** @@ -82,7 +82,7 @@ public function getDefaultInstance() */ public function setDefaultInstance($name) { - $this->app['config']['concurrency.default'] = $name; + $this->app['config']['concurrency.driver'] = $name; } /** @@ -94,7 +94,7 @@ public function setDefaultInstance($name) public function getInstanceConfig($name) { return $this->app['config']->get( - 'concurrency.drivers.'.$name, ['driver' => $name], + 'concurrency.driver.'.$name, ['driver' => $name], ); } } From 881b28d571a4061ce693960b9cf8010bd840e7a5 Mon Sep 17 00:00:00 2001 From: Volodya Kurshudyan <70023120+xurshudyan@users.noreply.github.com> Date: Mon, 16 Sep 2024 18:36:02 +0400 Subject: [PATCH 085/100] Fix withoutPretending method to correctly reset state after exception (#52794) Co-authored-by: Xurshudyan --- src/Illuminate/Database/Connection.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Database/Connection.php b/src/Illuminate/Database/Connection.php index 723b586ec251..a8d50dca1f22 100755 --- a/src/Illuminate/Database/Connection.php +++ b/src/Illuminate/Database/Connection.php @@ -672,11 +672,11 @@ public function withoutPretending(Closure $callback) $this->pretending = false; - $result = $callback(); - - $this->pretending = true; - - return $result; + try { + return $callback(); + } finally { + $this->pretending = true; + } } /** From 79fbf4a983c147c3bdef0045fa93aa4d8594c151 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Mon, 16 Sep 2024 12:10:51 -0300 Subject: [PATCH 086/100] delegate defer to run method (#52807) --- src/Illuminate/Concurrency/ProcessDriver.php | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/Illuminate/Concurrency/ProcessDriver.php b/src/Illuminate/Concurrency/ProcessDriver.php index 6c6300c99f32..775245212cc0 100644 --- a/src/Illuminate/Concurrency/ProcessDriver.php +++ b/src/Illuminate/Concurrency/ProcessDriver.php @@ -59,20 +59,7 @@ public function run(Closure|array $tasks): array */ public function defer(Closure|array $tasks): DeferredCallback { - $php = $this->phpBinary(); - $artisan = $this->artisanBinary(); - - return defer(function () use ($tasks, $php, $artisan) { - foreach (Arr::wrap($tasks) as $task) { - $this->processFactory->path(base_path())->env([ - 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), - ])->run([ - $php, - $artisan, - 'invoke-serialized-closure 2>&1 &', - ]); - } - }); + return defer(fn () => $this->run($tasks)); } /** From fc71e91fd9699aac20d7deb190d8fbcb01c31fbe Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Mon, 16 Sep 2024 14:54:21 -0300 Subject: [PATCH 087/100] [11.x] Use command string instead of array on `Concurrency\ProcessDriver` (#52813) * use command string instead of array * use Console/Application::formatCommandString() --- src/Illuminate/Concurrency/ProcessDriver.php | 37 +++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/src/Illuminate/Concurrency/ProcessDriver.php b/src/Illuminate/Concurrency/ProcessDriver.php index 775245212cc0..8ba3345f9006 100644 --- a/src/Illuminate/Concurrency/ProcessDriver.php +++ b/src/Illuminate/Concurrency/ProcessDriver.php @@ -3,13 +3,13 @@ namespace Illuminate\Concurrency; use Closure; +use Illuminate\Console\Application; use Illuminate\Contracts\Concurrency\Driver; use Illuminate\Foundation\Defer\DeferredCallback; use Illuminate\Process\Factory as ProcessFactory; use Illuminate\Process\Pool; use Illuminate\Support\Arr; use Laravel\SerializableClosure\SerializableClosure; -use Symfony\Component\Process\PhpExecutableFinder; class ProcessDriver implements Driver { @@ -26,18 +26,13 @@ public function __construct(protected ProcessFactory $processFactory) */ public function run(Closure|array $tasks): array { - $php = $this->phpBinary(); - $artisan = $this->artisanBinary(); + $command = Application::formatCommandString('invoke-serialized-closure'); - $results = $this->processFactory->pool(function (Pool $pool) use ($tasks, $php, $artisan) { + $results = $this->processFactory->pool(function (Pool $pool) use ($tasks, $command) { foreach (Arr::wrap($tasks) as $task) { $pool->path(base_path())->env([ 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), - ])->command([ - $php, - $artisan, - 'invoke-serialized-closure', - ]); + ])->command($command); } })->start()->wait(); @@ -59,22 +54,14 @@ public function run(Closure|array $tasks): array */ public function defer(Closure|array $tasks): DeferredCallback { - return defer(fn () => $this->run($tasks)); - } - - /** - * Get the PHP binary. - */ - protected function phpBinary(): string - { - return (new PhpExecutableFinder)->find(false) ?: 'php'; - } + $command = Application::formatCommandString('invoke-serialized-closure'); - /** - * Get the Artisan binary. - */ - protected function artisanBinary(): string - { - return defined('ARTISAN_BINARY') ? ARTISAN_BINARY : 'artisan'; + return defer(function () use ($tasks, $command) { + foreach (Arr::wrap($tasks) as $task) { + $this->processFactory->path(base_path())->env([ + 'LARAVEL_INVOKABLE_CLOSURE' => serialize(new SerializableClosure($task)), + ])->run($command.' 2>&1 &'); + } + }); } } From 15e0224750675787251d802bb0b59aa319e5dd82 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 16 Sep 2024 14:05:10 -0500 Subject: [PATCH 088/100] support driver --- src/Illuminate/Concurrency/ConcurrencyManager.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Concurrency/ConcurrencyManager.php b/src/Illuminate/Concurrency/ConcurrencyManager.php index 27646401bd21..830d41316d6b 100644 --- a/src/Illuminate/Concurrency/ConcurrencyManager.php +++ b/src/Illuminate/Concurrency/ConcurrencyManager.php @@ -71,7 +71,9 @@ public function createSyncDriver(array $config) */ public function getDefaultInstance() { - return $this->app['config']['concurrency.default'] ?? 'process'; + return $this->app['config']['concurrency.default'] + ?? $this->app['config']['concurrency.driver'] + ?? 'process'; } /** @@ -83,6 +85,7 @@ public function getDefaultInstance() public function setDefaultInstance($name) { $this->app['config']['concurrency.default'] = $name; + $this->app['config']['concurrency.driver'] = $name; } /** From 50205f4a13f71d4bd589de2378b289717b9293f3 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Mon, 16 Sep 2024 14:05:43 -0500 Subject: [PATCH 089/100] use default --- config/concurrency.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/concurrency.php b/config/concurrency.php index 1d66b701f823..cb8022b4669d 100644 --- a/config/concurrency.php +++ b/config/concurrency.php @@ -15,6 +15,6 @@ | */ - 'driver' => env('CONCURRENCY_DRIVER', 'process'), + 'default' => env('CONCURRENCY_DRIVER', 'process'), ]; From d58269058009fc962c013fcdbd2d1cb1f327ec1a Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 17 Sep 2024 22:52:04 +0800 Subject: [PATCH 090/100] [11.x] Allows Laravel Framework to correctly resolve PHP binary when running via Laravel Herd (#52791) * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki * Update src/Illuminate/Support/functions.php Co-authored-by: Marcel Pociot * Apply fixes from StyleCI --------- Signed-off-by: Mior Muhammad Zaki Co-authored-by: StyleCI Bot Co-authored-by: Marcel Pociot --- composer.json | 1 + src/Illuminate/Console/Application.php | 5 +++-- .../Console/InteractsWithComposerPackages.php | 5 +++-- src/Illuminate/Queue/Listener.php | 5 +++-- src/Illuminate/Support/Composer.php | 3 +-- .../Support/Process/PhpExecutableFinder.php | 21 +++++++++++++++++++ src/Illuminate/Support/composer.json | 1 + src/Illuminate/Support/functions.php | 15 +++++++++++++ 8 files changed, 48 insertions(+), 8 deletions(-) create mode 100644 src/Illuminate/Support/Process/PhpExecutableFinder.php create mode 100644 src/Illuminate/Support/functions.php diff --git a/composer.json b/composer.json index 8a78ce9b10cf..9a5f2b500b4c 100644 --- a/composer.json +++ b/composer.json @@ -133,6 +133,7 @@ "src/Illuminate/Filesystem/functions.php", "src/Illuminate/Foundation/helpers.php", "src/Illuminate/Log/functions.php", + "src/Illuminate/Support/functions.php", "src/Illuminate/Support/helpers.php" ], "psr-4": { diff --git a/src/Illuminate/Console/Application.php b/src/Illuminate/Console/Application.php index 8e92793855fc..55d1ca33eca2 100755 --- a/src/Illuminate/Console/Application.php +++ b/src/Illuminate/Console/Application.php @@ -16,7 +16,8 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\BufferedOutput; -use Symfony\Component\Process\PhpExecutableFinder; + +use function Illuminate\Support\php_binary; class Application extends SymfonyApplication implements ApplicationContract { @@ -84,7 +85,7 @@ public function __construct(Container $laravel, Dispatcher $events, $version) */ public static function phpBinary() { - return ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false) ?: 'php'); + return ProcessUtils::escapeArgument(php_binary()); } /** diff --git a/src/Illuminate/Foundation/Console/InteractsWithComposerPackages.php b/src/Illuminate/Foundation/Console/InteractsWithComposerPackages.php index ebd8b7e7b3d1..4628f2946b8d 100644 --- a/src/Illuminate/Foundation/Console/InteractsWithComposerPackages.php +++ b/src/Illuminate/Foundation/Console/InteractsWithComposerPackages.php @@ -2,9 +2,10 @@ namespace Illuminate\Foundation\Console; -use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; +use function Illuminate\Support\php_binary; + trait InteractsWithComposerPackages { /** @@ -39,6 +40,6 @@ protected function requireComposerPackages(string $composer, array $packages) */ protected function phpBinary() { - return (new PhpExecutableFinder())->find(false) ?: 'php'; + return php_binary(); } } diff --git a/src/Illuminate/Queue/Listener.php b/src/Illuminate/Queue/Listener.php index 58a28ee18284..271a7c3b4d8e 100755 --- a/src/Illuminate/Queue/Listener.php +++ b/src/Illuminate/Queue/Listener.php @@ -3,9 +3,10 @@ namespace Illuminate\Queue; use Closure; -use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; +use function Illuminate\Support\php_binary; + class Listener { /** @@ -61,7 +62,7 @@ public function __construct($commandPath) */ protected function phpBinary() { - return (new PhpExecutableFinder)->find(false) ?: 'php'; + return php_binary(); } /** diff --git a/src/Illuminate/Support/Composer.php b/src/Illuminate/Support/Composer.php index 3725394e017d..190500a4fe32 100644 --- a/src/Illuminate/Support/Composer.php +++ b/src/Illuminate/Support/Composer.php @@ -6,7 +6,6 @@ use Illuminate\Filesystem\Filesystem; use RuntimeException; use Symfony\Component\Console\Output\OutputInterface; -use Symfony\Component\Process\PhpExecutableFinder; use Symfony\Component\Process\Process; class Composer @@ -204,7 +203,7 @@ protected function findComposerFile() */ protected function phpBinary() { - return (new PhpExecutableFinder)->find(false) ?: 'php'; + return php_binary(); } /** diff --git a/src/Illuminate/Support/Process/PhpExecutableFinder.php b/src/Illuminate/Support/Process/PhpExecutableFinder.php new file mode 100644 index 000000000000..6b9698f85bf1 --- /dev/null +++ b/src/Illuminate/Support/Process/PhpExecutableFinder.php @@ -0,0 +1,21 @@ +find('php', false, [implode(DIRECTORY_SEPARATOR, [$herdPath, 'bin'])]); + } + + return parent::find($includeArgs); + } +} diff --git a/src/Illuminate/Support/composer.json b/src/Illuminate/Support/composer.json index 8995ed9655fc..3da999fd887f 100644 --- a/src/Illuminate/Support/composer.json +++ b/src/Illuminate/Support/composer.json @@ -37,6 +37,7 @@ "Illuminate\\Support\\": "" }, "files": [ + "functions.php", "helpers.php" ] }, diff --git a/src/Illuminate/Support/functions.php b/src/Illuminate/Support/functions.php new file mode 100644 index 000000000000..40e4d7befeea --- /dev/null +++ b/src/Illuminate/Support/functions.php @@ -0,0 +1,15 @@ +find(false) ?: 'php'; +} From 18c22d1e1d5b932adbc6d19159215125bf86607c Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Tue, 17 Sep 2024 23:56:18 +0800 Subject: [PATCH 091/100] [11.x] Move Defer classes to Support component and add `Illuminate\Support\defer` function (#52801) * [11.x] Move Defer classes to Concurrency component Also avoid using global `defer()` function when it can be defined in environment such as Swoole Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki * Update DeferredCallbackCollection.php * 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: Taylor Otwell --- src/Illuminate/Cache/Repository.php | 8 +++---- src/Illuminate/Concurrency/ForkDriver.php | 4 +++- src/Illuminate/Concurrency/ProcessDriver.php | 4 +++- src/Illuminate/Concurrency/SyncDriver.php | 4 +++- src/Illuminate/Concurrency/composer.json | 2 +- .../Contracts/Concurrency/Driver.php | 2 +- .../Middleware/InvokeDeferredCallbacks.php | 2 +- .../Providers/FoundationServiceProvider.php | 2 +- src/Illuminate/Foundation/helpers.php | 11 +--------- .../Defer/DeferredCallback.php | 2 +- .../Defer/DeferredCallbackCollection.php | 2 +- .../Support/Facades/Concurrency.php | 2 +- src/Illuminate/Support/functions.php | 22 +++++++++++++++++++ 13 files changed, 42 insertions(+), 25 deletions(-) rename src/Illuminate/{Foundation => Support}/Defer/DeferredCallback.php (96%) rename src/Illuminate/{Foundation => Support}/Defer/DeferredCallbackCollection.php (98%) diff --git a/src/Illuminate/Cache/Repository.php b/src/Illuminate/Cache/Repository.php index 77fbb52a2e5f..e5337cd4a094 100755 --- a/src/Illuminate/Cache/Repository.php +++ b/src/Illuminate/Cache/Repository.php @@ -24,6 +24,8 @@ use Illuminate\Support\InteractsWithTime; use Illuminate\Support\Traits\Macroable; +use function Illuminate\Support\defer; + /** * @mixin \Illuminate\Contracts\Cache\Store */ @@ -517,11 +519,7 @@ public function flexible($key, $ttl, $callback, $lock = null) }); }; - if (function_exists('defer')) { - defer($refresh, "illuminate:cache:refresh:{$key}"); - } else { - $refresh(); - } + defer($refresh, "illuminate:cache:refresh:{$key}"); return $value; } diff --git a/src/Illuminate/Concurrency/ForkDriver.php b/src/Illuminate/Concurrency/ForkDriver.php index a02ee54a94a8..b385e4fbf7e6 100644 --- a/src/Illuminate/Concurrency/ForkDriver.php +++ b/src/Illuminate/Concurrency/ForkDriver.php @@ -4,10 +4,12 @@ use Closure; use Illuminate\Contracts\Concurrency\Driver; -use Illuminate\Foundation\Defer\DeferredCallback; use Illuminate\Support\Arr; +use Illuminate\Support\Defer\DeferredCallback; use Spatie\Fork\Fork; +use function Illuminate\Support\defer; + class ForkDriver implements Driver { /** diff --git a/src/Illuminate/Concurrency/ProcessDriver.php b/src/Illuminate/Concurrency/ProcessDriver.php index 8ba3345f9006..0f4ac17363c0 100644 --- a/src/Illuminate/Concurrency/ProcessDriver.php +++ b/src/Illuminate/Concurrency/ProcessDriver.php @@ -5,12 +5,14 @@ use Closure; use Illuminate\Console\Application; use Illuminate\Contracts\Concurrency\Driver; -use Illuminate\Foundation\Defer\DeferredCallback; use Illuminate\Process\Factory as ProcessFactory; use Illuminate\Process\Pool; use Illuminate\Support\Arr; +use Illuminate\Support\Defer\DeferredCallback; use Laravel\SerializableClosure\SerializableClosure; +use function Illuminate\Support\defer; + class ProcessDriver implements Driver { /** diff --git a/src/Illuminate/Concurrency/SyncDriver.php b/src/Illuminate/Concurrency/SyncDriver.php index 62bb552ae664..ca43dc9d27c3 100644 --- a/src/Illuminate/Concurrency/SyncDriver.php +++ b/src/Illuminate/Concurrency/SyncDriver.php @@ -4,8 +4,10 @@ use Closure; use Illuminate\Contracts\Concurrency\Driver; -use Illuminate\Foundation\Defer\DeferredCallback; use Illuminate\Support\Arr; +use Illuminate\Support\Defer\DeferredCallback; + +use function Illuminate\Support\defer; class SyncDriver implements Driver { diff --git a/src/Illuminate/Concurrency/composer.json b/src/Illuminate/Concurrency/composer.json index 6476f73aafde..f7a9c57e37b8 100644 --- a/src/Illuminate/Concurrency/composer.json +++ b/src/Illuminate/Concurrency/composer.json @@ -20,7 +20,7 @@ }, "autoload": { "psr-4": { - "Illuminate\\Support\\": "" + "Illuminate\\Concurrency\\": "" } }, "extra": { diff --git a/src/Illuminate/Contracts/Concurrency/Driver.php b/src/Illuminate/Contracts/Concurrency/Driver.php index fd963ea6c672..901f613b349b 100644 --- a/src/Illuminate/Contracts/Concurrency/Driver.php +++ b/src/Illuminate/Contracts/Concurrency/Driver.php @@ -3,7 +3,7 @@ namespace Illuminate\Contracts\Concurrency; use Closure; -use Illuminate\Foundation\Defer\DeferredCallback; +use Illuminate\Support\Defer\DeferredCallback; interface Driver { diff --git a/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php b/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php index 9a804332e73e..9e31e026c87e 100644 --- a/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php +++ b/src/Illuminate/Foundation/Http/Middleware/InvokeDeferredCallbacks.php @@ -4,8 +4,8 @@ use Closure; use Illuminate\Container\Container; -use Illuminate\Foundation\Defer\DeferredCallbackCollection; use Illuminate\Http\Request; +use Illuminate\Support\Defer\DeferredCallbackCollection; use Symfony\Component\HttpFoundation\Response; class InvokeDeferredCallbacks diff --git a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php index 6711dd2d770f..be497edaf88e 100644 --- a/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php +++ b/src/Illuminate/Foundation/Providers/FoundationServiceProvider.php @@ -13,7 +13,6 @@ use Illuminate\Database\ConnectionInterface; use Illuminate\Database\Grammar; use Illuminate\Foundation\Console\CliDumper; -use Illuminate\Foundation\Defer\DeferredCallbackCollection; use Illuminate\Foundation\Exceptions\Renderer\Listener; use Illuminate\Foundation\Exceptions\Renderer\Mappers\BladeMapper; use Illuminate\Foundation\Exceptions\Renderer\Renderer; @@ -26,6 +25,7 @@ use Illuminate\Log\Events\MessageLogged; use Illuminate\Queue\Events\JobAttempted; use Illuminate\Support\AggregateServiceProvider; +use Illuminate\Support\Defer\DeferredCallbackCollection; use Illuminate\Support\Facades\URL; use Illuminate\Testing\LoggedExceptionCollection; use Illuminate\Testing\ParallelTestingServiceProvider; diff --git a/src/Illuminate/Foundation/helpers.php b/src/Illuminate/Foundation/helpers.php index 8ce88f468033..09224da3e685 100644 --- a/src/Illuminate/Foundation/helpers.php +++ b/src/Illuminate/Foundation/helpers.php @@ -14,8 +14,6 @@ use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Foundation\Bus\PendingClosureDispatch; use Illuminate\Foundation\Bus\PendingDispatch; -use Illuminate\Foundation\Defer\DeferredCallback; -use Illuminate\Foundation\Defer\DeferredCallbackCollection; use Illuminate\Foundation\Mix; use Illuminate\Http\Exceptions\HttpResponseException; use Illuminate\Log\Context\Repository as ContextRepository; @@ -412,14 +410,7 @@ function decrypt($value, $unserialize = true) */ function defer(?callable $callback = null, ?string $name = null, bool $always = false) { - if ($callback === null) { - return app(DeferredCallbackCollection::class); - } - - return tap( - new DeferredCallback($callback, $name, $always), - fn ($deferred) => app(DeferredCallbackCollection::class)[] = $deferred - ); + return \Illuminate\Support\defer($callback, $name, $always); } } diff --git a/src/Illuminate/Foundation/Defer/DeferredCallback.php b/src/Illuminate/Support/Defer/DeferredCallback.php similarity index 96% rename from src/Illuminate/Foundation/Defer/DeferredCallback.php rename to src/Illuminate/Support/Defer/DeferredCallback.php index 14b8a85690dd..2bf6ad4cbd1c 100644 --- a/src/Illuminate/Foundation/Defer/DeferredCallback.php +++ b/src/Illuminate/Support/Defer/DeferredCallback.php @@ -1,6 +1,6 @@ app(DeferredCallbackCollection::class)[] = $deferred + ); +} + /** * Determine the PHP Binary. * From b4d9257da02087250e7a82a352f200780dde7f91 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Wed, 18 Sep 2024 22:22:23 +0800 Subject: [PATCH 092/100] [11.x] Suggest `laravel/serializable-closure` on Database component (#52835) Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Database/composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Illuminate/Database/composer.json b/src/Illuminate/Database/composer.json index bcbb837c0818..ace45ba91b77 100644 --- a/src/Illuminate/Database/composer.json +++ b/src/Illuminate/Database/composer.json @@ -41,6 +41,7 @@ "illuminate/events": "Required to use the observers with Eloquent (^11.0).", "illuminate/filesystem": "Required to use the migrations (^11.0).", "illuminate/pagination": "Required to paginate the result set (^11.0).", + "laravel/serializable-closure": "Required to handle circular references in model serialization (^1.3).", "symfony/finder": "Required to use Eloquent model factories (^7.0)." }, "config": { From 3072be043e05e71341f9bd0397713ee6965acb71 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Sep 2024 09:23:30 -0500 Subject: [PATCH 093/100] Bump vite in /src/Illuminate/Foundation/resources/exceptions/renderer (#52834) Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.10 to 5.2.14. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v5.2.14/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v5.2.14/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../resources/exceptions/renderer/package-lock.json | 8 ++++---- .../Foundation/resources/exceptions/renderer/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json b/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json index f7d870c1517b..b7ae4d135af6 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/package-lock.json @@ -11,7 +11,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "tippy.js": "^6.3.7", - "vite": "^5.2.10", + "vite": "^5.2.14", "vite-require": "^0.2.3" } }, @@ -2079,9 +2079,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.10.tgz", - "integrity": "sha512-PAzgUZbP7msvQvqdSD+ErD5qGnSFiGOoWmV5yAKUEI0kdhjbH6nMWVyZQC/hSc4aXwc0oJ9aEdIiF9Oje0JFCw==", + "version": "5.2.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.14.tgz", + "integrity": "sha512-TFQLuwWLPms+NBNlh0D9LZQ+HXW471COABxw/9TEUBrjuHMo9BrYBPrN/SYAwIuVL+rLerycxiLT41t4f5MZpA==", "dependencies": { "esbuild": "^0.20.1", "postcss": "^8.4.38", diff --git a/src/Illuminate/Foundation/resources/exceptions/renderer/package.json b/src/Illuminate/Foundation/resources/exceptions/renderer/package.json index eca012037278..af1daa2d9496 100644 --- a/src/Illuminate/Foundation/resources/exceptions/renderer/package.json +++ b/src/Illuminate/Foundation/resources/exceptions/renderer/package.json @@ -12,7 +12,7 @@ "postcss": "^8.4.38", "tailwindcss": "^3.4.3", "tippy.js": "^6.3.7", - "vite": "^5.2.10", + "vite": "^5.2.14", "vite-require": "^0.2.3" } } From f859f7b5bd2f6625043328741aa2970175d98168 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Wed, 18 Sep 2024 11:23:55 -0300 Subject: [PATCH 094/100] update Concurrency component's composer dependencies (#52836) --- src/Illuminate/Concurrency/composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Illuminate/Concurrency/composer.json b/src/Illuminate/Concurrency/composer.json index f7a9c57e37b8..cd53b14077a2 100644 --- a/src/Illuminate/Concurrency/composer.json +++ b/src/Illuminate/Concurrency/composer.json @@ -15,7 +15,10 @@ ], "require": { "php": "^8.2", + "illuminate/console": "^11.0", + "illuminate/contracts": "^11.0", "illuminate/process": "^11.0", + "illuminate/support": "^11.0", "laravel/serializable-closure": "^1.2.2" }, "autoload": { From f243db71351433dd5050c1a3b445be7fcf156c52 Mon Sep 17 00:00:00 2001 From: Jason McCreary Date: Wed, 18 Sep 2024 10:56:49 -0400 Subject: [PATCH 095/100] Add testing shorthands for fakes (#52840) --- src/Illuminate/Process/PendingProcess.php | 5 +++++ tests/Process/ProcessTest.php | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/Illuminate/Process/PendingProcess.php b/src/Illuminate/Process/PendingProcess.php index 48a28f6b335a..3a4309f56d21 100644 --- a/src/Illuminate/Process/PendingProcess.php +++ b/src/Illuminate/Process/PendingProcess.php @@ -364,6 +364,10 @@ protected function resolveSynchronousFake(string $command, Closure $fake) { $result = $fake($this); + if (is_int($result)) { + return (new FakeProcessResult(exitCode: $result))->withCommand($command); + } + if (is_string($result) || is_array($result)) { return (new FakeProcessResult(output: $result))->withCommand($command); } @@ -373,6 +377,7 @@ protected function resolveSynchronousFake(string $command, Closure $fake) $result instanceof FakeProcessResult => $result->withCommand($command), $result instanceof FakeProcessDescription => $result->toProcessResult($command), $result instanceof FakeProcessSequence => $this->resolveSynchronousFake($command, fn () => $result()), + $result instanceof \Throwable => throw $result, default => throw new LogicException('Unsupported synchronous process fake result provided.'), }; } diff --git a/tests/Process/ProcessTest.php b/tests/Process/ProcessTest.php index 6e5790cfa590..841860cacd26 100644 --- a/tests/Process/ProcessTest.php +++ b/tests/Process/ProcessTest.php @@ -178,6 +178,16 @@ public function testProcessFakeExitCodes() $this->assertFalse($result->successful()); } + public function testProcessFakeExitCodeShorthand() + { + $factory = new Factory; + $factory->fake(['ls -la' => 1]); + + $result = $factory->run('ls -la'); + $this->assertSame(1, $result->exitCode()); + $this->assertFalse($result->successful()); + } + public function testBasicProcessFakeWithCustomOutput() { $factory = new Factory; @@ -389,6 +399,18 @@ public function testStrayProcessesActuallyRunByDefault() $this->assertTrue(str_contains($result->output(), 'ProcessTest.php')); } + public function testProcessFakeThrowShorthand() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('fake exception message'); + + $factory = new Factory; + + $factory->fake(['cat me' => new \RuntimeException('fake exception message')]); + + $factory->run('cat me'); + } + public function testFakeProcessesCanThrow() { $this->expectException(ProcessFailedException::class); From 3e56cc4cd0700dbcb540a40f263e4180867549e1 Mon Sep 17 00:00:00 2001 From: Luke Kuzmish <42181698+cosmastech@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:55:39 -0400 Subject: [PATCH 096/100] Update SerializesCastableAttributes.php (#52841) --- .../Database/Eloquent/SerializesCastableAttributes.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Contracts/Database/Eloquent/SerializesCastableAttributes.php b/src/Illuminate/Contracts/Database/Eloquent/SerializesCastableAttributes.php index a2ecf16f59bd..244f34705c6e 100644 --- a/src/Illuminate/Contracts/Database/Eloquent/SerializesCastableAttributes.php +++ b/src/Illuminate/Contracts/Database/Eloquent/SerializesCastableAttributes.php @@ -12,7 +12,7 @@ interface SerializesCastableAttributes * @param \Illuminate\Database\Eloquent\Model $model * @param string $key * @param mixed $value - * @param array $attributes + * @param array $attributes * @return mixed */ public function serialize(Model $model, string $key, mixed $value, array $attributes); From 2714f96613dc60a1268a30ee8b5ceefccd910b43 Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 19 Sep 2024 18:22:02 +0800 Subject: [PATCH 097/100] [11.x] CI Improvements (#52850) Disable SQL Server 2017 Signed-off-by: Mior Muhammad Zaki --- .github/workflows/databases.yml | 98 +++++++++++++++++---------------- 1 file changed, 52 insertions(+), 46 deletions(-) diff --git a/.github/workflows/databases.yml b/.github/workflows/databases.yml index f4286e7b10af..a656fea5be88 100644 --- a/.github/workflows/databases.yml +++ b/.github/workflows/databases.yml @@ -10,6 +10,7 @@ on: jobs: mysql_57: runs-on: ubuntu-24.04 + timeout-minutes: 5 services: mysql: @@ -56,6 +57,7 @@ jobs: mysql_8: runs-on: ubuntu-24.04 + timeout-minutes: 5 services: mysql: @@ -101,6 +103,7 @@ jobs: mariadb: runs-on: ubuntu-24.04 + timeout-minutes: 5 services: mariadb: @@ -146,6 +149,7 @@ jobs: pgsql_14: runs-on: ubuntu-24.04 + timeout-minutes: 5 services: postgresql: @@ -194,6 +198,7 @@ jobs: pgsql_10: runs-on: ubuntu-24.04 + timeout-minutes: 5 services: postgresql: @@ -287,55 +292,56 @@ jobs: DB_USERNAME: SA DB_PASSWORD: Forge123 - mssql_2017: - runs-on: ubuntu-22.04 - - services: - sqlsrv: - image: mcr.microsoft.com/mssql/server:2017-latest - env: - ACCEPT_EULA: Y - SA_PASSWORD: Forge123 - ports: - - 1433:1433 - - strategy: - fail-fast: true - - name: SQL Server 2017 - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.2 - extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr - tools: composer:v2 - coverage: none - - - name: Set Framework version - run: composer config version "11.x-dev" - - - name: Install dependencies - uses: nick-fields/retry@v3 - with: - timeout_minutes: 5 - max_attempts: 5 - command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress - - - name: Execute tests - run: vendor/bin/phpunit tests/Integration/Database - env: - DB_CONNECTION: sqlsrv - DB_DATABASE: master - DB_USERNAME: SA - DB_PASSWORD: Forge123 + # mssql_2017: + # runs-on: ubuntu-22.04 + + # services: + # sqlsrv: + # image: mcr.microsoft.com/mssql/server:2017-latest + # env: + # ACCEPT_EULA: Y + # SA_PASSWORD: Forge123 + # ports: + # - 1433:1433 + + # strategy: + # fail-fast: true + + # name: SQL Server 2017 + + # steps: + # - name: Checkout code + # uses: actions/checkout@v4 + + # - name: Setup PHP + # uses: shivammathur/setup-php@v2 + # with: + # php-version: 8.2 + # extensions: dom, curl, libxml, mbstring, zip, pcntl, sqlsrv, pdo, pdo_sqlsrv, odbc, pdo_odbc, :php-psr + # tools: composer:v2 + # coverage: none + + # - name: Set Framework version + # run: composer config version "11.x-dev" + + # - name: Install dependencies + # uses: nick-fields/retry@v3 + # with: + # timeout_minutes: 5 + # max_attempts: 5 + # command: composer update --prefer-stable --prefer-dist --no-interaction --no-progress + + # - name: Execute tests + # run: vendor/bin/phpunit tests/Integration/Database + # env: + # DB_CONNECTION: sqlsrv + # DB_DATABASE: master + # DB_USERNAME: SA + # DB_PASSWORD: Forge123 sqlite: runs-on: ubuntu-24.04 + timeout-minutes: 5 strategy: fail-fast: true From 4c7850844e218b4fa25e29c83bc80b7ef06836cb Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Thu, 19 Sep 2024 19:14:40 +0800 Subject: [PATCH 098/100] [11.x] Supports `laravel/prompts` v0.2 (#52849) Signed-off-by: Mior Muhammad Zaki --- composer.json | 2 +- src/Illuminate/Console/composer.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 9a5f2b500b4c..20263946a03b 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,7 @@ "fruitcake/php-cors": "^1.3", "guzzlehttp/guzzle": "^7.8", "guzzlehttp/uri-template": "^1.0", - "laravel/prompts": "^0.1.18", + "laravel/prompts": "^0.1.18|^0.2.0", "laravel/serializable-closure": "^1.3", "league/commonmark": "^2.2.1", "league/flysystem": "^3.8.0", diff --git a/src/Illuminate/Console/composer.json b/src/Illuminate/Console/composer.json index 6284998cd1d9..32cbb0a91817 100755 --- a/src/Illuminate/Console/composer.json +++ b/src/Illuminate/Console/composer.json @@ -21,7 +21,7 @@ "illuminate/macroable": "^11.0", "illuminate/support": "^11.0", "illuminate/view": "^11.0", - "laravel/prompts": "^0.1.12", + "laravel/prompts": "^0.1.18|^0.2.0", "nunomaduro/termwind": "^2.0", "symfony/console": "^7.0", "symfony/polyfill-php83": "^1.28", From 580197a6b3e2492fc2342c09767a41d4b0d20f5b Mon Sep 17 00:00:00 2001 From: Mior Muhammad Zaki Date: Fri, 20 Sep 2024 20:51:05 +0800 Subject: [PATCH 099/100] [11.x] Handle allows null parameter instead of requiring default value (#52866) * [11.x] Handle allows null parameter instead of requiring default value Signed-off-by: Mior Muhammad Zaki * wip Signed-off-by: Mior Muhammad Zaki --------- Signed-off-by: Mior Muhammad Zaki --- src/Illuminate/Container/Container.php | 4 ++ .../ContextualAttributeBindingTest.php | 48 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/src/Illuminate/Container/Container.php b/src/Illuminate/Container/Container.php index 829b797404b1..bd75a478ad17 100755 --- a/src/Illuminate/Container/Container.php +++ b/src/Illuminate/Container/Container.php @@ -1089,6 +1089,10 @@ protected function resolvePrimitive(ReflectionParameter $parameter) return []; } + if ($parameter->hasType() && $parameter->allowsNull()) { + return null; + } + $this->unresolvablePrimitive($parameter); } diff --git a/tests/Container/ContextualAttributeBindingTest.php b/tests/Container/ContextualAttributeBindingTest.php index f13e16599797..99354543ba66 100644 --- a/tests/Container/ContextualAttributeBindingTest.php +++ b/tests/Container/ContextualAttributeBindingTest.php @@ -228,6 +228,7 @@ public function testAttributeOnAppCall() $container->singleton('config', fn () => new Repository([ 'app' => [ 'timezone' => 'Europe/Paris', + 'locale' => null, ], ])); @@ -236,6 +237,35 @@ public function testAttributeOnAppCall() }); $this->assertEquals('Europe/Paris', $value); + + $value = $container->call(function (#[Config('app.locale')] ?string $value) { + return $value; + }); + + $this->assertNull($value); + } + + public function testNestedAttributeOnAppCall() + { + $container = new Container; + $container->singleton('config', fn () => new Repository([ + 'app' => [ + 'timezone' => 'Europe/Paris', + 'locale' => null, + ], + ])); + + $value = $container->call(function (TimezoneObject $object) { + return $object; + }); + + $this->assertEquals('Europe/Paris', $value->timezone); + + $value = $container->call(function (LocaleObject $object) { + return $object; + }); + + $this->assertNull($value->locale); } public function testTagAttribute() @@ -404,3 +434,21 @@ public function __construct(#[Storage('foo')] Filesystem $foo, #[Storage('bar')] { } } + +final class TimezoneObject +{ + public function __construct( + #[Config('app.timezone')] public readonly ?string $timezone + ) { + // + } +} + +final class LocaleObject +{ + public function __construct( + #[Config('app.locale')] public readonly ?string $locale + ) { + // + } +} From abf86b08b08c5ea5c9247877bc8b6ea259805f80 Mon Sep 17 00:00:00 2001 From: Raymond Nambaale Date: Fri, 20 Sep 2024 15:52:21 +0300 Subject: [PATCH 100/100] Adds `@throws` section to Conccurrency manager docs (#52856) --- src/Illuminate/Concurrency/ConcurrencyManager.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Illuminate/Concurrency/ConcurrencyManager.php b/src/Illuminate/Concurrency/ConcurrencyManager.php index f8c472a37695..1f97a0e992c4 100644 --- a/src/Illuminate/Concurrency/ConcurrencyManager.php +++ b/src/Illuminate/Concurrency/ConcurrencyManager.php @@ -39,6 +39,8 @@ public function createProcessDriver(array $config) * * @param array $config * @return \Illuminate\Concurrency\ForkDriver + * + * @throws \RuntimeException */ public function createForkDriver(array $config) {