From cfad4d7bf4922a27028c82b22d6e81af53e403c8 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 01:01:55 -0300 Subject: [PATCH 01/13] Test createOrFirst within a transaction on MySQL --- .../DatabaseEloquentMySqlIntegrationTest.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php b/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php index bad64969d83f..f7eaccdb274b 100644 --- a/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php +++ b/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; class DatabaseEloquentMySqlIntegrationTest extends MySqlTestCase @@ -57,6 +58,22 @@ public function testCreateOrFirst() $this->assertSame('Nuno Maduro', $user4->name); } + + public function testCreateOrFirstWithinTransaction() + { + $user1 = DatabaseEloquentMySqlIntegrationUser::createOrFirst(['email' => 'taylor@laravel.com']); + + DB::transaction(function () use ($user1) { + $user2 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( + ['email' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylor@laravel.com', $user2->email); + $this->assertNull($user2->name); + }); + } } class DatabaseEloquentMySqlIntegrationUser extends Model From ce2db3638231eac607ba19fc141b5a08c17e2760 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 01:44:01 -0300 Subject: [PATCH 02/13] Wraps the create part of the createOrFirst in a savepoint if needed --- src/Illuminate/Database/Eloquent/Builder.php | 19 ++++++++++++++++++- .../DatabaseEloquentIntegrationTest.php | 16 ++++++++++++++++ ...atabaseEloquentPostgresIntegrationTest.php | 17 +++++++++++++++++ ...tabaseEloquentSqlServerIntegrationTest.php | 17 +++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index a0bbcdb967fe..fda3ca15324c 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -580,7 +580,9 @@ public function firstOrCreate(array $attributes = [], array $values = []) public function createOrFirst(array $attributes = [], array $values = []) { try { - return $this->create(array_merge($attributes, $values)); + return $this->withSavepointIfNeeded(function () use ($attributes, $values) { + return $this->create(array_merge($attributes, $values)); + }); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first(); } @@ -2029,4 +2031,19 @@ public function __clone() { $this->query = clone $this->query; } + + /** + * Wraps the given Closure with a Transaction savepoint if needed. + * + * @template TModelValue + * + * @param \Closure(): TModelValue $default + * @return TModelValue + */ + protected function withSavepointIfNeeded(Closure $scope): mixed + { + return $this->getQuery()->getConnection()->transactionLevel() > 0 + ? $this->getQuery()->getConnection()->transaction($scope) + : $scope(); + } } diff --git a/tests/Database/DatabaseEloquentIntegrationTest.php b/tests/Database/DatabaseEloquentIntegrationTest.php index aa34e8d3b172..ff497a92a979 100644 --- a/tests/Database/DatabaseEloquentIntegrationTest.php +++ b/tests/Database/DatabaseEloquentIntegrationTest.php @@ -552,6 +552,22 @@ public function testCreateOrFirst() $this->assertSame('Nuno Maduro', $user4->name); } + public function testCreateOrFirstWithinTransaction() + { + $user1 = EloquentTestUniqueUser::create(['email' => 'taylorotwell@gmail.com']); + + DB::transaction(function () use ($user1) { + $user2 = EloquentTestUniqueUser::createOrFirst( + ['email' => 'taylorotwell@gmail.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylorotwell@gmail.com', $user2->email); + $this->assertNull($user2->name); + }); + } + public function testUpdateOrCreate() { $user1 = EloquentTestUser::create(['email' => 'taylorotwell@gmail.com']); diff --git a/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php b/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php index 7bc71f3fdc57..d95dca61a0cb 100644 --- a/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php +++ b/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; class DatabaseEloquentPostgresIntegrationTest extends PostgresTestCase @@ -57,6 +58,22 @@ public function testCreateOrFirst() $this->assertSame('Nuno Maduro', $user4->name); } + + public function testCreateOrFirstWithinTransaction() + { + $user1 = DatabaseEloquentPostgresIntegrationUser::create(['email' => 'taylor@laravel.com']); + + DB::transaction(function () use ($user1) { + $user2 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( + ['email' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylor@laravel.com', $user2->email); + $this->assertNull($user2->name); + }); + } } class DatabaseEloquentPostgresIntegrationUser extends Model diff --git a/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php b/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php index d7502edcfe44..9098e6608c6c 100644 --- a/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php +++ b/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; class DatabaseEloquentSqlServerIntegrationTest extends SqlServerTestCase @@ -57,6 +58,22 @@ public function testCreateOrFirst() $this->assertSame('Nuno Maduro', $user4->name); } + + public function testCreateOrFirstWithinTransaction() + { + $user1 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst(['email' => 'taylor@laravel.com']); + + DB::transaction(function () use ($user1) { + $user2 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( + ['email' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylor@laravel.com', $user2->email); + $this->assertNull($user2->name); + }); + } } class DatabaseEloquentSqlServerIntegrationUser extends Model From 9f45005639fe3bdd66a9e9e501e5acb50e7150e3 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 01:53:19 -0300 Subject: [PATCH 03/13] Wraps the create and attach parts of the createOrFirst within a transaction savepoint if needed --- src/Illuminate/Database/Eloquent/Builder.php | 2 +- .../Database/Eloquent/Relations/BelongsToMany.php | 8 ++++++-- .../Database/EloquentBelongsToManyTest.php | 13 +++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index fda3ca15324c..343b0c3328c9 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -2040,7 +2040,7 @@ public function __clone() * @param \Closure(): TModelValue $default * @return TModelValue */ - protected function withSavepointIfNeeded(Closure $scope): mixed + public function withSavepointIfNeeded(Closure $scope): mixed { return $this->getQuery()->getConnection()->transactionLevel() > 0 ? $this->getQuery()->getConnection()->transaction($scope) diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index a4cc76fb0308..436d82de0a85 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -643,14 +643,18 @@ public function firstOrCreate(array $attributes = [], array $values = [], array public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true) { try { - return $this->create(array_merge($attributes, $values), $joining, $touch); + return $this->getQuery()->withSavePointIfNeeded(function () use ($attributes, $values, $joining, $touch) { + return $this->create(array_merge($attributes, $values), $joining, $touch); + }); } catch (UniqueConstraintViolationException $exception) { // ... } try { return tap($this->related->where($attributes)->first(), function ($instance) use ($joining, $touch) { - $this->attach($instance, $joining, $touch); + $this->getQuery()->withSavepointIfNeeded(function () use ($instance, $joining, $touch) { + $this->attach($instance, $joining, $touch); + }); }); } catch (UniqueConstraintViolationException $exception) { return (clone $this)->where($attributes)->first(); diff --git a/tests/Integration/Database/EloquentBelongsToManyTest.php b/tests/Integration/Database/EloquentBelongsToManyTest.php index ca4b15894613..23e6eb518bb7 100644 --- a/tests/Integration/Database/EloquentBelongsToManyTest.php +++ b/tests/Integration/Database/EloquentBelongsToManyTest.php @@ -629,6 +629,19 @@ public function testCreateOrFirstUnrelatedExisting() $this->assertTrue($tag->is($post->tagsUnique()->first())); } + public function testCreateOrFirstWithinTransaction() + { + $post = Post::create(['title' => Str::random()]); + + $tag = UniqueTag::create(['name' => Str::random()]); + + $post->tagsUnique()->attach(UniqueTag::all()); + + DB::transaction(function () use ($tag, $post) { + $this->assertEquals($tag->id, $post->tagsUnique()->createOrFirst(['name' => $tag->name])->id); + }); + } + public function testFirstOrNewMethodWithValues() { $post = Post::create(['title' => Str::random()]); From dbc41c34628453a40f02bf9dabd8c174575de823 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 01:57:17 -0300 Subject: [PATCH 04/13] Wraps the create part of the createOrFirst in a savepoint if needed --- .../Database/Eloquent/Relations/HasOneOrMany.php | 4 +++- .../Integration/Database/EloquentHasManyTest.php | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index 482e5208a946..59309b4d9391 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -252,7 +252,9 @@ public function firstOrCreate(array $attributes = [], array $values = []) public function createOrFirst(array $attributes = [], array $values = []) { try { - return $this->create(array_merge($attributes, $values)); + return $this->getQuery()->withSavepointIfNeeded(function () use ($attributes, $values) { + return $this->create(array_merge($attributes, $values)); + }); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first(); } diff --git a/tests/Integration/Database/EloquentHasManyTest.php b/tests/Integration/Database/EloquentHasManyTest.php index ac8e9b21446e..49e1480ba9c7 100644 --- a/tests/Integration/Database/EloquentHasManyTest.php +++ b/tests/Integration/Database/EloquentHasManyTest.php @@ -5,6 +5,7 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; @@ -70,6 +71,21 @@ public function testCreateOrFirst() $this->assertTrue($post1->is($post2)); $this->assertCount(1, $user->posts()->get()); } + + public function testCreateOrFirstWithinTransaction() + { + $user = EloquentHasManyTestUser::create(); + + $post1 = $user->posts()->create(['title' => Str::random()]); + + DB::transaction(function () use ($user, $post1) { + $post2 = $user->posts()->createOrFirst(['title' => $post1->title]); + + $this->assertTrue($post1->is($post2)); + }); + + $this->assertCount(1, $user->posts()->get()); + } } class EloquentHasManyTestUser extends Model From d4dd95bbb63c667932443055bd70d87198feb87d Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 02:03:48 -0300 Subject: [PATCH 05/13] Fix comments --- src/Illuminate/Database/Eloquent/Builder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 343b0c3328c9..01d9da09a034 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -2033,11 +2033,11 @@ public function __clone() } /** - * Wraps the given Closure with a Transaction savepoint if needed. + * Wraps the given Closure with a transaction savepoint if needed. * * @template TModelValue * - * @param \Closure(): TModelValue $default + * @param \Closure(): TModelValue $scope * @return TModelValue */ public function withSavepointIfNeeded(Closure $scope): mixed From a7e57c84ef8036e3dfb052d073943b1e9ad6ceb0 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 02:27:47 -0300 Subject: [PATCH 06/13] Moves the eloquent contract tests to a trait --- .../DatabaseEloquentMySqlIntegrationTest.php | 55 ++---------------- ...atabaseEloquentPostgresIntegrationTest.php | 55 ++---------------- ...tabaseEloquentSqlServerIntegrationTest.php | 55 ++---------------- .../Database/WithEloquentIntegrationTests.php | 57 +++++++++++++++++++ 4 files changed, 72 insertions(+), 150 deletions(-) create mode 100644 tests/Integration/Database/WithEloquentIntegrationTests.php diff --git a/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php b/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php index f7eaccdb274b..2df663e3b86d 100644 --- a/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php +++ b/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php @@ -4,11 +4,15 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use Illuminate\Tests\Integration\Database\WithEloquentIntegrationTests; class DatabaseEloquentMySqlIntegrationTest extends MySqlTestCase { + use WithEloquentIntegrationTests; + + protected $eloquentModelClass = DatabaseEloquentMySqlIntegrationUser::class; + protected function defineDatabaseMigrationsAfterDatabaseRefreshed() { if (! Schema::hasTable('database_eloquent_mysql_integration_users')) { @@ -25,55 +29,6 @@ protected function destroyDatabaseMigrations() { Schema::drop('database_eloquent_mysql_integration_users'); } - - public function testCreateOrFirst() - { - $user1 = DatabaseEloquentMySqlIntegrationUser::createOrFirst(['email' => 'taylorotwell@gmail.com']); - - $this->assertSame('taylorotwell@gmail.com', $user1->email); - $this->assertNull($user1->name); - - $user2 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( - ['email' => 'taylorotwell@gmail.com'], - ['name' => 'Taylor Otwell'] - ); - - $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylorotwell@gmail.com', $user2->email); - $this->assertNull($user2->name); - - $user3 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( - ['email' => 'abigailotwell@gmail.com'], - ['name' => 'Abigail Otwell'] - ); - - $this->assertNotEquals($user3->id, $user1->id); - $this->assertSame('abigailotwell@gmail.com', $user3->email); - $this->assertSame('Abigail Otwell', $user3->name); - - $user4 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( - ['name' => 'Dries Vints'], - ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] - ); - - $this->assertSame('Nuno Maduro', $user4->name); - } - - public function testCreateOrFirstWithinTransaction() - { - $user1 = DatabaseEloquentMySqlIntegrationUser::createOrFirst(['email' => 'taylor@laravel.com']); - - DB::transaction(function () use ($user1) { - $user2 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( - ['email' => 'taylor@laravel.com'], - ['name' => 'Taylor Otwell'] - ); - - $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylor@laravel.com', $user2->email); - $this->assertNull($user2->name); - }); - } } class DatabaseEloquentMySqlIntegrationUser extends Model diff --git a/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php b/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php index d95dca61a0cb..2470dc6c822f 100644 --- a/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php +++ b/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php @@ -4,11 +4,15 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use Illuminate\Tests\Integration\Database\WithEloquentIntegrationTests; class DatabaseEloquentPostgresIntegrationTest extends PostgresTestCase { + use WithEloquentIntegrationTests; + + protected $eloquentModelClass = DatabaseEloquentPostgresIntegrationUser::class; + protected function defineDatabaseMigrationsAfterDatabaseRefreshed() { if (! Schema::hasTable('database_eloquent_postgres_integration_users')) { @@ -25,55 +29,6 @@ protected function destroyDatabaseMigrations() { Schema::drop('database_eloquent_postgres_integration_users'); } - - public function testCreateOrFirst() - { - $user1 = DatabaseEloquentPostgresIntegrationUser::createOrFirst(['email' => 'taylorotwell@gmail.com']); - - $this->assertSame('taylorotwell@gmail.com', $user1->email); - $this->assertNull($user1->name); - - $user2 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( - ['email' => 'taylorotwell@gmail.com'], - ['name' => 'Taylor Otwell'] - ); - - $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylorotwell@gmail.com', $user2->email); - $this->assertNull($user2->name); - - $user3 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( - ['email' => 'abigailotwell@gmail.com'], - ['name' => 'Abigail Otwell'] - ); - - $this->assertNotEquals($user3->id, $user1->id); - $this->assertSame('abigailotwell@gmail.com', $user3->email); - $this->assertSame('Abigail Otwell', $user3->name); - - $user4 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( - ['name' => 'Dries Vints'], - ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] - ); - - $this->assertSame('Nuno Maduro', $user4->name); - } - - public function testCreateOrFirstWithinTransaction() - { - $user1 = DatabaseEloquentPostgresIntegrationUser::create(['email' => 'taylor@laravel.com']); - - DB::transaction(function () use ($user1) { - $user2 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( - ['email' => 'taylor@laravel.com'], - ['name' => 'Taylor Otwell'] - ); - - $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylor@laravel.com', $user2->email); - $this->assertNull($user2->name); - }); - } } class DatabaseEloquentPostgresIntegrationUser extends Model diff --git a/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php b/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php index 9098e6608c6c..694eca76e830 100644 --- a/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php +++ b/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php @@ -4,11 +4,15 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; +use Illuminate\Tests\Integration\Database\WithEloquentIntegrationTests; class DatabaseEloquentSqlServerIntegrationTest extends SqlServerTestCase { + use WithEloquentIntegrationTests; + + protected $eloquentModelClass = DatabaseEloquentSqlServerIntegrationUser::class; + protected function defineDatabaseMigrationsAfterDatabaseRefreshed() { if (! Schema::hasTable('database_eloquent_sql_server_integration_users')) { @@ -25,55 +29,6 @@ protected function destroyDatabaseMigrations() { Schema::drop('database_eloquent_sql_server_integration_users'); } - - public function testCreateOrFirst() - { - $user1 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst(['email' => 'taylorotwell@gmail.com']); - - $this->assertSame('taylorotwell@gmail.com', $user1->email); - $this->assertNull($user1->name); - - $user2 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( - ['email' => 'taylorotwell@gmail.com'], - ['name' => 'Taylor Otwell'] - ); - - $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylorotwell@gmail.com', $user2->email); - $this->assertNull($user2->name); - - $user3 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( - ['email' => 'abigailotwell@gmail.com'], - ['name' => 'Abigail Otwell'] - ); - - $this->assertNotEquals($user3->id, $user1->id); - $this->assertSame('abigailotwell@gmail.com', $user3->email); - $this->assertSame('Abigail Otwell', $user3->name); - - $user4 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( - ['name' => 'Dries Vints'], - ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] - ); - - $this->assertSame('Nuno Maduro', $user4->name); - } - - public function testCreateOrFirstWithinTransaction() - { - $user1 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst(['email' => 'taylor@laravel.com']); - - DB::transaction(function () use ($user1) { - $user2 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( - ['email' => 'taylor@laravel.com'], - ['name' => 'Taylor Otwell'] - ); - - $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylor@laravel.com', $user2->email); - $this->assertNull($user2->name); - }); - } } class DatabaseEloquentSqlServerIntegrationUser extends Model diff --git a/tests/Integration/Database/WithEloquentIntegrationTests.php b/tests/Integration/Database/WithEloquentIntegrationTests.php new file mode 100644 index 000000000000..7ad66edd1557 --- /dev/null +++ b/tests/Integration/Database/WithEloquentIntegrationTests.php @@ -0,0 +1,57 @@ +eloquentModelClass::createOrFirst(['email' => 'taylorotwell@gmail.com']); + + $this->assertSame('taylorotwell@gmail.com', $user1->email); + $this->assertNull($user1->name); + + $user2 = $this->eloquentModelClass::createOrFirst( + ['email' => 'taylorotwell@gmail.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylorotwell@gmail.com', $user2->email); + $this->assertNull($user2->name); + + $user3 = $this->eloquentModelClass::createOrFirst( + ['email' => 'abigailotwell@gmail.com'], + ['name' => 'Abigail Otwell'] + ); + + $this->assertNotEquals($user3->id, $user1->id); + $this->assertSame('abigailotwell@gmail.com', $user3->email); + $this->assertSame('Abigail Otwell', $user3->name); + + $user4 = $this->eloquentModelClass::createOrFirst( + ['name' => 'Dries Vints'], + ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] + ); + + $this->assertSame('Nuno Maduro', $user4->name); + } + + public function testCreateOrFirstWithinTransaction() + { + $user1 = $this->eloquentModelClass::createOrFirst(['email' => 'taylor@laravel.com']); + + DB::transaction(function () use ($user1) { + $user2 = $this->eloquentModelClass::createOrFirst( + ['email' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylor@laravel.com', $user2->email); + $this->assertNull($user2->name); + }); + } +} From fabfff88c1aa998f8c8b951af96f3941319b4328 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 02:34:43 -0300 Subject: [PATCH 07/13] Fix the tests using Mocks --- tests/Database/DatabaseEloquentHasManyTest.php | 9 +++++++++ tests/Database/DatabaseEloquentMorphTest.php | 12 ++++++++++++ 2 files changed, 21 insertions(+) diff --git a/tests/Database/DatabaseEloquentHasManyTest.php b/tests/Database/DatabaseEloquentHasManyTest.php index 8fcfa74a6b17..a10e884cd6e9 100755 --- a/tests/Database/DatabaseEloquentHasManyTest.php +++ b/tests/Database/DatabaseEloquentHasManyTest.php @@ -181,6 +181,9 @@ public function testCreateOrFirstMethodWithValuesFindsFirstModel() ); })); + $relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) { + return $scope(); + }); $relation->getQuery()->shouldReceive('where')->once()->with(['foo' => 'bar'])->andReturn($relation->getQuery()); $relation->getQuery()->shouldReceive('first')->once()->with()->andReturn($model = m::mock(stdClass::class)); @@ -192,6 +195,9 @@ public function testCreateOrFirstMethodCreatesNewModelWithForeignKeySet() { $relation = $this->getRelation(); + $relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) { + return $scope(); + }); $relation->getQuery()->shouldReceive('where')->never(); $relation->getQuery()->shouldReceive('first')->never(); $model = $this->expectCreatedModel($relation, ['foo']); @@ -202,6 +208,9 @@ public function testCreateOrFirstMethodCreatesNewModelWithForeignKeySet() public function testCreateOrFirstMethodWithValuesCreatesNewModelWithForeignKeySet() { $relation = $this->getRelation(); + $relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) { + return $scope(); + }); $relation->getQuery()->shouldReceive('where')->never(); $relation->getQuery()->shouldReceive('first')->never(); $model = $this->expectCreatedModel($relation, ['foo' => 'bar', 'baz' => 'qux']); diff --git a/tests/Database/DatabaseEloquentMorphTest.php b/tests/Database/DatabaseEloquentMorphTest.php index d30eee15d7cd..ed17b3a092a5 100755 --- a/tests/Database/DatabaseEloquentMorphTest.php +++ b/tests/Database/DatabaseEloquentMorphTest.php @@ -227,6 +227,9 @@ public function testCreateOrFirstMethodFindsFirstModel() new UniqueConstraintViolationException('mysql', 'example mysql', [], new Exception('SQLSTATE[23000]: Integrity constraint violation: 1062')), ); + $relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) { + return $scope(); + }); $relation->getQuery()->shouldReceive('where')->once()->with(['foo'])->andReturn($relation->getQuery()); $relation->getQuery()->shouldReceive('first')->once()->with()->andReturn($model = m::mock(Model::class)); @@ -244,6 +247,9 @@ public function testCreateOrFirstMethodWithValuesFindsFirstModel() new UniqueConstraintViolationException('mysql', 'example mysql', [], new Exception('SQLSTATE[23000]: Integrity constraint violation: 1062')), ); + $relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) { + return $scope(); + }); $relation->getQuery()->shouldReceive('where')->once()->with(['foo' => 'bar'])->andReturn($relation->getQuery()); $relation->getQuery()->shouldReceive('first')->once()->with()->andReturn($model = m::mock(Model::class)); @@ -259,6 +265,9 @@ public function testCreateOrFirstMethodCreatesNewMorphModel() $model->shouldReceive('setAttribute')->once()->with('morph_type', get_class($relation->getParent())); $model->shouldReceive('save')->once()->andReturn(true); + $relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) { + return $scope(); + }); $relation->getQuery()->shouldReceive('where')->never(); $relation->getQuery()->shouldReceive('first')->never(); @@ -274,6 +283,9 @@ public function testCreateOrFirstMethodWithValuesCreatesNewMorphModel() $model->shouldReceive('setAttribute')->once()->with('morph_type', get_class($relation->getParent())); $model->shouldReceive('save')->once()->andReturn(true); + $relation->getQuery()->shouldReceive('withSavepointIfNeeded')->once()->andReturnUsing(function ($scope) { + return $scope(); + }); $relation->getQuery()->shouldReceive('where')->never(); $relation->getQuery()->shouldReceive('first')->never(); From 6854a55dfb234295edd28b4afd20b7b72d3bb2b0 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 02:52:06 -0300 Subject: [PATCH 08/13] Revert "Moves the eloquent contract tests to a trait" This reverts commit a7e57c84ef8036e3dfb052d073943b1e9ad6ceb0. --- .../DatabaseEloquentMySqlIntegrationTest.php | 55 ++++++++++++++++-- ...atabaseEloquentPostgresIntegrationTest.php | 55 ++++++++++++++++-- ...tabaseEloquentSqlServerIntegrationTest.php | 55 ++++++++++++++++-- .../Database/WithEloquentIntegrationTests.php | 57 ------------------- 4 files changed, 150 insertions(+), 72 deletions(-) delete mode 100644 tests/Integration/Database/WithEloquentIntegrationTests.php diff --git a/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php b/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php index 2df663e3b86d..f7eaccdb274b 100644 --- a/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php +++ b/tests/Integration/Database/MySql/DatabaseEloquentMySqlIntegrationTest.php @@ -4,15 +4,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -use Illuminate\Tests\Integration\Database\WithEloquentIntegrationTests; class DatabaseEloquentMySqlIntegrationTest extends MySqlTestCase { - use WithEloquentIntegrationTests; - - protected $eloquentModelClass = DatabaseEloquentMySqlIntegrationUser::class; - protected function defineDatabaseMigrationsAfterDatabaseRefreshed() { if (! Schema::hasTable('database_eloquent_mysql_integration_users')) { @@ -29,6 +25,55 @@ protected function destroyDatabaseMigrations() { Schema::drop('database_eloquent_mysql_integration_users'); } + + public function testCreateOrFirst() + { + $user1 = DatabaseEloquentMySqlIntegrationUser::createOrFirst(['email' => 'taylorotwell@gmail.com']); + + $this->assertSame('taylorotwell@gmail.com', $user1->email); + $this->assertNull($user1->name); + + $user2 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( + ['email' => 'taylorotwell@gmail.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylorotwell@gmail.com', $user2->email); + $this->assertNull($user2->name); + + $user3 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( + ['email' => 'abigailotwell@gmail.com'], + ['name' => 'Abigail Otwell'] + ); + + $this->assertNotEquals($user3->id, $user1->id); + $this->assertSame('abigailotwell@gmail.com', $user3->email); + $this->assertSame('Abigail Otwell', $user3->name); + + $user4 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( + ['name' => 'Dries Vints'], + ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] + ); + + $this->assertSame('Nuno Maduro', $user4->name); + } + + public function testCreateOrFirstWithinTransaction() + { + $user1 = DatabaseEloquentMySqlIntegrationUser::createOrFirst(['email' => 'taylor@laravel.com']); + + DB::transaction(function () use ($user1) { + $user2 = DatabaseEloquentMySqlIntegrationUser::createOrFirst( + ['email' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylor@laravel.com', $user2->email); + $this->assertNull($user2->name); + }); + } } class DatabaseEloquentMySqlIntegrationUser extends Model diff --git a/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php b/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php index 2470dc6c822f..d95dca61a0cb 100644 --- a/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php +++ b/tests/Integration/Database/Postgres/DatabaseEloquentPostgresIntegrationTest.php @@ -4,15 +4,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -use Illuminate\Tests\Integration\Database\WithEloquentIntegrationTests; class DatabaseEloquentPostgresIntegrationTest extends PostgresTestCase { - use WithEloquentIntegrationTests; - - protected $eloquentModelClass = DatabaseEloquentPostgresIntegrationUser::class; - protected function defineDatabaseMigrationsAfterDatabaseRefreshed() { if (! Schema::hasTable('database_eloquent_postgres_integration_users')) { @@ -29,6 +25,55 @@ protected function destroyDatabaseMigrations() { Schema::drop('database_eloquent_postgres_integration_users'); } + + public function testCreateOrFirst() + { + $user1 = DatabaseEloquentPostgresIntegrationUser::createOrFirst(['email' => 'taylorotwell@gmail.com']); + + $this->assertSame('taylorotwell@gmail.com', $user1->email); + $this->assertNull($user1->name); + + $user2 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( + ['email' => 'taylorotwell@gmail.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylorotwell@gmail.com', $user2->email); + $this->assertNull($user2->name); + + $user3 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( + ['email' => 'abigailotwell@gmail.com'], + ['name' => 'Abigail Otwell'] + ); + + $this->assertNotEquals($user3->id, $user1->id); + $this->assertSame('abigailotwell@gmail.com', $user3->email); + $this->assertSame('Abigail Otwell', $user3->name); + + $user4 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( + ['name' => 'Dries Vints'], + ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] + ); + + $this->assertSame('Nuno Maduro', $user4->name); + } + + public function testCreateOrFirstWithinTransaction() + { + $user1 = DatabaseEloquentPostgresIntegrationUser::create(['email' => 'taylor@laravel.com']); + + DB::transaction(function () use ($user1) { + $user2 = DatabaseEloquentPostgresIntegrationUser::createOrFirst( + ['email' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylor@laravel.com', $user2->email); + $this->assertNull($user2->name); + }); + } } class DatabaseEloquentPostgresIntegrationUser extends Model diff --git a/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php b/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php index 694eca76e830..9098e6608c6c 100644 --- a/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php +++ b/tests/Integration/Database/SqlServer/DatabaseEloquentSqlServerIntegrationTest.php @@ -4,15 +4,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Schema; -use Illuminate\Tests\Integration\Database\WithEloquentIntegrationTests; class DatabaseEloquentSqlServerIntegrationTest extends SqlServerTestCase { - use WithEloquentIntegrationTests; - - protected $eloquentModelClass = DatabaseEloquentSqlServerIntegrationUser::class; - protected function defineDatabaseMigrationsAfterDatabaseRefreshed() { if (! Schema::hasTable('database_eloquent_sql_server_integration_users')) { @@ -29,6 +25,55 @@ protected function destroyDatabaseMigrations() { Schema::drop('database_eloquent_sql_server_integration_users'); } + + public function testCreateOrFirst() + { + $user1 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst(['email' => 'taylorotwell@gmail.com']); + + $this->assertSame('taylorotwell@gmail.com', $user1->email); + $this->assertNull($user1->name); + + $user2 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( + ['email' => 'taylorotwell@gmail.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylorotwell@gmail.com', $user2->email); + $this->assertNull($user2->name); + + $user3 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( + ['email' => 'abigailotwell@gmail.com'], + ['name' => 'Abigail Otwell'] + ); + + $this->assertNotEquals($user3->id, $user1->id); + $this->assertSame('abigailotwell@gmail.com', $user3->email); + $this->assertSame('Abigail Otwell', $user3->name); + + $user4 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( + ['name' => 'Dries Vints'], + ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] + ); + + $this->assertSame('Nuno Maduro', $user4->name); + } + + public function testCreateOrFirstWithinTransaction() + { + $user1 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst(['email' => 'taylor@laravel.com']); + + DB::transaction(function () use ($user1) { + $user2 = DatabaseEloquentSqlServerIntegrationUser::createOrFirst( + ['email' => 'taylor@laravel.com'], + ['name' => 'Taylor Otwell'] + ); + + $this->assertEquals($user1->id, $user2->id); + $this->assertSame('taylor@laravel.com', $user2->email); + $this->assertNull($user2->name); + }); + } } class DatabaseEloquentSqlServerIntegrationUser extends Model diff --git a/tests/Integration/Database/WithEloquentIntegrationTests.php b/tests/Integration/Database/WithEloquentIntegrationTests.php deleted file mode 100644 index 7ad66edd1557..000000000000 --- a/tests/Integration/Database/WithEloquentIntegrationTests.php +++ /dev/null @@ -1,57 +0,0 @@ -eloquentModelClass::createOrFirst(['email' => 'taylorotwell@gmail.com']); - - $this->assertSame('taylorotwell@gmail.com', $user1->email); - $this->assertNull($user1->name); - - $user2 = $this->eloquentModelClass::createOrFirst( - ['email' => 'taylorotwell@gmail.com'], - ['name' => 'Taylor Otwell'] - ); - - $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylorotwell@gmail.com', $user2->email); - $this->assertNull($user2->name); - - $user3 = $this->eloquentModelClass::createOrFirst( - ['email' => 'abigailotwell@gmail.com'], - ['name' => 'Abigail Otwell'] - ); - - $this->assertNotEquals($user3->id, $user1->id); - $this->assertSame('abigailotwell@gmail.com', $user3->email); - $this->assertSame('Abigail Otwell', $user3->name); - - $user4 = $this->eloquentModelClass::createOrFirst( - ['name' => 'Dries Vints'], - ['name' => 'Nuno Maduro', 'email' => 'nuno@laravel.com'] - ); - - $this->assertSame('Nuno Maduro', $user4->name); - } - - public function testCreateOrFirstWithinTransaction() - { - $user1 = $this->eloquentModelClass::createOrFirst(['email' => 'taylor@laravel.com']); - - DB::transaction(function () use ($user1) { - $user2 = $this->eloquentModelClass::createOrFirst( - ['email' => 'taylor@laravel.com'], - ['name' => 'Taylor Otwell'] - ); - - $this->assertEquals($user1->id, $user2->id); - $this->assertSame('taylor@laravel.com', $user2->email); - $this->assertNull($user2->name); - }); - } -} From c9c2c6a14f8e5d1b00218ae173a32db2bc9df5bc Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 02:58:05 -0300 Subject: [PATCH 09/13] Only use savepoint for postgres --- src/Illuminate/Database/Eloquent/Builder.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 01d9da09a034..4fc6586aadb9 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -12,6 +12,7 @@ use Illuminate\Database\Eloquent\Concerns\QueriesRelationships; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Database\PostgresConnection; use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Database\RecordsNotFoundException; use Illuminate\Database\UniqueConstraintViolationException; @@ -2042,8 +2043,17 @@ public function __clone() */ public function withSavepointIfNeeded(Closure $scope): mixed { - return $this->getQuery()->getConnection()->transactionLevel() > 0 + return $this->needsSavepoint() ? $this->getQuery()->getConnection()->transaction($scope) : $scope(); } + + protected function needsSavePoint(): bool + { + if (! $this->getQuery()->getConnection() instanceof PostgresConnection) { + return false; + } + + return $this->getQuery()->getConnection()->transactionLevel() > 0; + } } From 06dc97e812516299af7d25ecb6733855cbe4dd41 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 03:01:14 -0300 Subject: [PATCH 10/13] Revert "Only use savepoint for postgres" This reverts commit c9c2c6a14f8e5d1b00218ae173a32db2bc9df5bc. --- src/Illuminate/Database/Eloquent/Builder.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 4fc6586aadb9..01d9da09a034 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -12,7 +12,6 @@ use Illuminate\Database\Eloquent\Concerns\QueriesRelationships; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\Relation; -use Illuminate\Database\PostgresConnection; use Illuminate\Database\Query\Builder as QueryBuilder; use Illuminate\Database\RecordsNotFoundException; use Illuminate\Database\UniqueConstraintViolationException; @@ -2043,17 +2042,8 @@ public function __clone() */ public function withSavepointIfNeeded(Closure $scope): mixed { - return $this->needsSavepoint() + return $this->getQuery()->getConnection()->transactionLevel() > 0 ? $this->getQuery()->getConnection()->transaction($scope) : $scope(); } - - protected function needsSavePoint(): bool - { - if (! $this->getQuery()->getConnection() instanceof PostgresConnection) { - return false; - } - - return $this->getQuery()->getConnection()->transactionLevel() > 0; - } } From 73a438262ca03b044ef01ac8542bf5f96ab54f31 Mon Sep 17 00:00:00 2001 From: Taylor Otwell Date: Wed, 23 Aug 2023 10:27:24 -0500 Subject: [PATCH 11/13] use short closures. formatting --- src/Illuminate/Database/Eloquent/Builder.php | 36 +++++++++---------- .../Eloquent/Relations/BelongsToMany.php | 12 +++---- .../Eloquent/Relations/HasOneOrMany.php | 6 ++-- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 01d9da09a034..18df4f3c26d4 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -580,9 +580,9 @@ public function firstOrCreate(array $attributes = [], array $values = []) public function createOrFirst(array $attributes = [], array $values = []) { try { - return $this->withSavepointIfNeeded(function () use ($attributes, $values) { - return $this->create(array_merge($attributes, $values)); - }); + return $this->withSavepointIfNeeded(fn () => + $this->create(array_merge($attributes, $values)) + ); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first(); } @@ -1711,6 +1711,21 @@ public function withCasts($casts) return $this; } + /** + * Execute the given Closure within a transaction savepoint if needed. + * + * @template TModelValue + * + * @param \Closure(): TModelValue $scope + * @return TModelValue + */ + public function withSavepointIfNeeded(Closure $scope): mixed + { + return $this->getQuery()->getConnection()->transactionLevel() > 0 + ? $this->getQuery()->getConnection()->transaction($scope) + : $scope(); + } + /** * Get the underlying query builder instance. * @@ -2031,19 +2046,4 @@ public function __clone() { $this->query = clone $this->query; } - - /** - * Wraps the given Closure with a transaction savepoint if needed. - * - * @template TModelValue - * - * @param \Closure(): TModelValue $scope - * @return TModelValue - */ - public function withSavepointIfNeeded(Closure $scope): mixed - { - return $this->getQuery()->getConnection()->transactionLevel() > 0 - ? $this->getQuery()->getConnection()->transaction($scope) - : $scope(); - } } diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 436d82de0a85..861c44883653 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -643,18 +643,18 @@ public function firstOrCreate(array $attributes = [], array $values = [], array public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true) { try { - return $this->getQuery()->withSavePointIfNeeded(function () use ($attributes, $values, $joining, $touch) { - return $this->create(array_merge($attributes, $values), $joining, $touch); - }); + return $this->getQuery()->withSavePointIfNeeded(fn () => + $this->create(array_merge($attributes, $values), $joining, $touch) + ); } catch (UniqueConstraintViolationException $exception) { // ... } try { return tap($this->related->where($attributes)->first(), function ($instance) use ($joining, $touch) { - $this->getQuery()->withSavepointIfNeeded(function () use ($instance, $joining, $touch) { - $this->attach($instance, $joining, $touch); - }); + $this->getQuery()->withSavepointIfNeeded(fn () => + $this->attach($instance, $joining, $touch) + ); }); } catch (UniqueConstraintViolationException $exception) { return (clone $this)->where($attributes)->first(); diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index 59309b4d9391..c3afa7aae6db 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -252,9 +252,9 @@ public function firstOrCreate(array $attributes = [], array $values = []) public function createOrFirst(array $attributes = [], array $values = []) { try { - return $this->getQuery()->withSavepointIfNeeded(function () use ($attributes, $values) { - return $this->create(array_merge($attributes, $values)); - }); + return $this->getQuery()->withSavepointIfNeeded(fn () => + $this->create(array_merge($attributes, $values)) + ); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first(); } From 38f2bb287f766853a1dcf02013fb298d45e266f9 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 12:56:14 -0300 Subject: [PATCH 12/13] StyleCI --- src/Illuminate/Database/Eloquent/Builder.php | 3 +-- .../Database/Eloquent/Relations/BelongsToMany.php | 6 ++---- src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 18df4f3c26d4..515c1709cc23 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -580,8 +580,7 @@ public function firstOrCreate(array $attributes = [], array $values = []) public function createOrFirst(array $attributes = [], array $values = []) { try { - return $this->withSavepointIfNeeded(fn () => - $this->create(array_merge($attributes, $values)) + return $this->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values)) ); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first(); diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 861c44883653..3edcfcb66462 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -643,8 +643,7 @@ public function firstOrCreate(array $attributes = [], array $values = [], array public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true) { try { - return $this->getQuery()->withSavePointIfNeeded(fn () => - $this->create(array_merge($attributes, $values), $joining, $touch) + return $this->getQuery()->withSavePointIfNeeded(fn () => $this->create(array_merge($attributes, $values), $joining, $touch) ); } catch (UniqueConstraintViolationException $exception) { // ... @@ -652,8 +651,7 @@ public function createOrFirst(array $attributes = [], array $values = [], array try { return tap($this->related->where($attributes)->first(), function ($instance) use ($joining, $touch) { - $this->getQuery()->withSavepointIfNeeded(fn () => - $this->attach($instance, $joining, $touch) + $this->getQuery()->withSavepointIfNeeded(fn () => $this->attach($instance, $joining, $touch) ); }); } catch (UniqueConstraintViolationException $exception) { diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index c3afa7aae6db..3f32586cd960 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -252,8 +252,7 @@ public function firstOrCreate(array $attributes = [], array $values = []) public function createOrFirst(array $attributes = [], array $values = []) { try { - return $this->getQuery()->withSavepointIfNeeded(fn () => - $this->create(array_merge($attributes, $values)) + return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values)) ); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first(); From 17451690f05aa9dcb262c54facb0683051ed45ff Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Wed, 23 Aug 2023 13:04:51 -0300 Subject: [PATCH 13/13] Fix StyleCI formatting --- src/Illuminate/Database/Eloquent/Builder.php | 3 +-- .../Database/Eloquent/Relations/BelongsToMany.php | 6 ++---- src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/Illuminate/Database/Eloquent/Builder.php b/src/Illuminate/Database/Eloquent/Builder.php index 515c1709cc23..2e17922c5353 100755 --- a/src/Illuminate/Database/Eloquent/Builder.php +++ b/src/Illuminate/Database/Eloquent/Builder.php @@ -580,8 +580,7 @@ public function firstOrCreate(array $attributes = [], array $values = []) public function createOrFirst(array $attributes = [], array $values = []) { try { - return $this->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values)) - ); + return $this->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values))); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first(); } diff --git a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php index 3edcfcb66462..4a443ddece10 100755 --- a/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/BelongsToMany.php @@ -643,16 +643,14 @@ public function firstOrCreate(array $attributes = [], array $values = [], array public function createOrFirst(array $attributes = [], array $values = [], array $joining = [], $touch = true) { try { - return $this->getQuery()->withSavePointIfNeeded(fn () => $this->create(array_merge($attributes, $values), $joining, $touch) - ); + return $this->getQuery()->withSavePointIfNeeded(fn () => $this->create(array_merge($attributes, $values), $joining, $touch)); } catch (UniqueConstraintViolationException $exception) { // ... } try { return tap($this->related->where($attributes)->first(), function ($instance) use ($joining, $touch) { - $this->getQuery()->withSavepointIfNeeded(fn () => $this->attach($instance, $joining, $touch) - ); + $this->getQuery()->withSavepointIfNeeded(fn () => $this->attach($instance, $joining, $touch)); }); } catch (UniqueConstraintViolationException $exception) { return (clone $this)->where($attributes)->first(); diff --git a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php index 3f32586cd960..109cb017dc95 100755 --- a/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php +++ b/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php @@ -252,8 +252,7 @@ public function firstOrCreate(array $attributes = [], array $values = []) public function createOrFirst(array $attributes = [], array $values = []) { try { - return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values)) - ); + return $this->getQuery()->withSavepointIfNeeded(fn () => $this->create(array_merge($attributes, $values))); } catch (UniqueConstraintViolationException $exception) { return $this->where($attributes)->first(); }