Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[10.x] Fix "after commit" callbacks not running on nested transaction…
…s using `RefreshDatabase` or `DatabaseMigrations` (#48523) * Tests observer using afterCommit * Adds failing test for savepoint and observers using afterCommit * Ignore the createOrFirst savepoint to run callbacks * Fix typo * Remove DatabaseEloquentAppTest to see if CI passes * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * Update src/Illuminate/Database/DatabaseTransactionsManager.php Co-authored-by: Mior Muhammad Zaki <crynobone@gmail.com> * Changes the way the after commit callbacks are executed to avoid remembering which transactions to ignore Before, we were remembering the test transaction so we could ignore it when deciding to run the after commit callbacks or not. We're still handling the after commit callbacks like that but now instead of remembering which transactions to ignore, we're always calling the DatabaseTransactionManager::commit method. The difference is that now we're passing the current transaction level to it. The method will decide to call the callbacks or not based on that level and whether or not this is on in test mode. When in tests, instead of setting the current transaction to be remembered so it could be ignored, we're now only setting the DatabaseTransactionManager to test mode. When in test mode, it will execute the callbacks when the transactions count reaches 1 (remember that the test runs in a transaction, so that's the "root" level). Otherwise, it runs the callbacks when the transactions level is on level 0 (like in production). There's also a change in the DatabaseTransactionManager::addCallback method. It now also checks if it's in test mode. When not in test mode, it only adds the callback to the execution queue if there's an open transaction. Otherwise, the callback is executed right away. When in test mode, the number of transactions has to be greater than one for it to be added to the callbacks queue. * Fix DatabaseTransactionsTest * Fix DatabaseTransactionsManagerTest * CSFixer * wip * Rename method and property and inline usage * Adds a depply nested transaction test * Simplify deeply nesting test * Sets default level value to one (since it's a new parameter) * Rename method * Adds back the removed methods from the db.transactions and mark them as deprecated * StyleCI * Inline the if statement using then collection when() method * Tests observer using afterCommit Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * Apply fixes from StyleCI * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * wip Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> * Update src/Illuminate/Foundation/Testing/DatabaseTransactionsManager.php Co-authored-by: Tony Messias <tonysm@hey.com> * Update src/Illuminate/Database/DatabaseTransactionsManager.php * formatting --------- Signed-off-by: Mior Muhammad Zaki <crynobone@gmail.com> Co-authored-by: Tony Messias <tonysm@hey.com> Co-authored-by: Taylor Otwell <taylor@laravel.com> Co-authored-by: StyleCI Bot <bot@styleci.io>
- Loading branch information
1 parent
169c976
commit ebcb151
Showing
11 changed files
with
275 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
src/Illuminate/Foundation/Testing/DatabaseTransactionsManager.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
<?php | ||
|
||
namespace Illuminate\Foundation\Testing; | ||
|
||
use Illuminate\Database\DatabaseTransactionsManager as BaseManager; | ||
|
||
class DatabaseTransactionsManager extends BaseManager | ||
{ | ||
/** | ||
* Register a transaction callback. | ||
* | ||
* @param callable $callback | ||
* @return void | ||
*/ | ||
public function addCallback($callback) | ||
{ | ||
// If there are no transactions, we'll run the callbacks right away. Also, we'll run it | ||
// right away when we're in test mode and we only have the wrapping transaction. For | ||
// every other case, we'll queue up the callback to run after the commit happens. | ||
if ($this->callbackApplicableTransactions()->count() === 0) { | ||
return $callback(); | ||
} | ||
|
||
$this->transactions->last()->addCallback($callback); | ||
} | ||
|
||
/** | ||
* Get the transactions that are applicable to callbacks. | ||
* | ||
* @return \Illuminate\Support\Collection<int, \Illuminate\Database\DatabaseTransactionRecord> | ||
*/ | ||
public function callbackApplicableTransactions() | ||
{ | ||
return $this->transactions->skip(1)->values(); | ||
} | ||
|
||
/** | ||
* Determine if after commit callbacks should be executed for the given transaction level. | ||
* | ||
* @param int $level | ||
* @return bool | ||
*/ | ||
public function afterCommitCallbacksShouldBeExecuted($level) | ||
{ | ||
return $level === 2; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 8 additions & 0 deletions
8
tests/Integration/Database/EloquentTransactionWithAfterCommitTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<?php | ||
|
||
namespace Illuminate\Tests\Integration\Database; | ||
|
||
class EloquentTransactionWithAfterCommitTest extends DatabaseTestCase | ||
{ | ||
use EloquentTransactionWithAfterCommitTests; | ||
} |
104 changes: 104 additions & 0 deletions
104
tests/Integration/Database/EloquentTransactionWithAfterCommitTests.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?php | ||
|
||
namespace Illuminate\Tests\Integration\Database; | ||
|
||
use Illuminate\Foundation\Auth\User; | ||
use Illuminate\Support\Facades\DB; | ||
use Orchestra\Testbench\Concerns\WithLaravelMigrations; | ||
use Orchestra\Testbench\Factories\UserFactory; | ||
|
||
trait EloquentTransactionWithAfterCommitTests | ||
{ | ||
use WithLaravelMigrations; | ||
|
||
protected function setUpEloquentTransactionWithAfterCommitTests(): void | ||
{ | ||
User::unguard(); | ||
} | ||
|
||
protected function tearDownEloquentTransactionWithAfterCommitTests(): void | ||
{ | ||
User::reguard(); | ||
} | ||
|
||
public function testObserverIsCalledOnTestsWithAfterCommit() | ||
{ | ||
User::observe($observer = EloquentTransactionWithAfterCommitTestsUserObserver::resetting()); | ||
|
||
$user1 = User::create(UserFactory::new()->raw()); | ||
|
||
$this->assertTrue($user1->exists); | ||
$this->assertEquals(1, $observer::$calledTimes, 'Failed to assert the observer was called once.'); | ||
} | ||
|
||
public function testObserverCalledWithAfterCommitWhenInsideTransaction() | ||
{ | ||
User::observe($observer = EloquentTransactionWithAfterCommitTestsUserObserver::resetting()); | ||
|
||
$user1 = DB::transaction(fn () => User::create(UserFactory::new()->raw())); | ||
|
||
$this->assertTrue($user1->exists); | ||
$this->assertEquals(1, $observer::$calledTimes, 'Failed to assert the observer was called once.'); | ||
} | ||
|
||
public function testObserverIsCalledOnTestsWithAfterCommitWhenUsingSavepoint() | ||
{ | ||
User::observe($observer = EloquentTransactionWithAfterCommitTestsUserObserver::resetting()); | ||
|
||
$user1 = User::createOrFirst(UserFactory::new()->raw()); | ||
|
||
$this->assertTrue($user1->exists); | ||
$this->assertEquals(1, $observer::$calledTimes, 'Failed to assert the observer was called once.'); | ||
} | ||
|
||
public function testObserverIsCalledOnTestsWithAfterCommitWhenUsingSavepointAndInsideTransaction() | ||
{ | ||
User::observe($observer = EloquentTransactionWithAfterCommitTestsUserObserver::resetting()); | ||
|
||
$user1 = DB::transaction(fn () => User::createOrFirst(UserFactory::new()->raw())); | ||
|
||
$this->assertTrue($user1->exists); | ||
$this->assertEquals(1, $observer::$calledTimes, 'Failed to assert the observer was called once.'); | ||
} | ||
|
||
public function testObserverIsCalledEvenWhenDeeplyNestingTransactions() | ||
{ | ||
User::observe($observer = EloquentTransactionWithAfterCommitTestsUserObserver::resetting()); | ||
|
||
$user1 = DB::transaction(function () use ($observer) { | ||
return tap(DB::transaction(function () use ($observer) { | ||
return tap(DB::transaction(function () use ($observer) { | ||
return tap(User::createOrFirst(UserFactory::new()->raw()), function () use ($observer) { | ||
$this->assertEquals(0, $observer::$calledTimes, 'Should not have been called'); | ||
}); | ||
}), function () use ($observer) { | ||
$this->assertEquals(0, $observer::$calledTimes, 'Should not have been called'); | ||
}); | ||
}), function () use ($observer) { | ||
$this->assertEquals(0, $observer::$calledTimes, 'Should not have been called'); | ||
}); | ||
}); | ||
|
||
$this->assertTrue($user1->exists); | ||
$this->assertEquals(1, $observer::$calledTimes, 'Failed to assert the observer was called once.'); | ||
} | ||
} | ||
|
||
class EloquentTransactionWithAfterCommitTestsUserObserver | ||
{ | ||
public static $calledTimes = 0; | ||
|
||
public $afterCommit = true; | ||
|
||
public static function resetting() | ||
{ | ||
static::$calledTimes = 0; | ||
|
||
return new static(); | ||
} | ||
|
||
public function created($user) | ||
{ | ||
static::$calledTimes++; | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
tests/Integration/Database/EloquentTransactionWithAfterCommitUsingDatabaseMigrationsTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
<?php | ||
|
||
namespace Illuminate\Tests\Integration\Database; | ||
|
||
use Illuminate\Foundation\Testing\DatabaseMigrations; | ||
|
||
class EloquentTransactionWithAfterCommitUsingDatabaseMigrationsTest extends DatabaseTestCase | ||
{ | ||
use EloquentTransactionWithAfterCommitTests; | ||
use DatabaseMigrations; | ||
} |
41 changes: 41 additions & 0 deletions
41
.../Integration/Database/EloquentTransactionWithAfterCommitUsingDatabaseTransactionsTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
<?php | ||
|
||
namespace Illuminate\Tests\Integration\Database; | ||
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions; | ||
use Orchestra\Testbench\TestCase; | ||
|
||
class EloquentTransactionWithAfterCommitUsingDatabaseTransactionsTest extends TestCase | ||
{ | ||
use EloquentTransactionWithAfterCommitTests; | ||
use DatabaseTransactions; | ||
|
||
/** | ||
* The current database driver. | ||
* | ||
* @return string | ||
*/ | ||
protected $driver; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->beforeApplicationDestroyed(function () { | ||
foreach (array_keys($this->app['db']->getConnections()) as $name) { | ||
$this->app['db']->purge($name); | ||
} | ||
}); | ||
|
||
parent::setUp(); | ||
|
||
if ($this->usesSqliteInMemoryDatabaseConnection()) { | ||
$this->markTestSkipped('Test cannot be used with in-memory SQLite connection.'); | ||
} | ||
} | ||
|
||
protected function getEnvironmentSetUp($app) | ||
{ | ||
$connection = $app->make('config')->get('database.default'); | ||
|
||
$this->driver = $app['config']->get("database.connections.$connection.driver"); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
tests/Integration/Database/EloquentTransactionWithAfterCommitUsingRefreshDatabaseTest.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
namespace Illuminate\Tests\Integration\Database; | ||
|
||
use Illuminate\Foundation\Testing\RefreshDatabase; | ||
use Orchestra\Testbench\TestCase; | ||
|
||
class EloquentTransactionWithAfterCommitUsingRefreshDatabaseTest extends TestCase | ||
{ | ||
use EloquentTransactionWithAfterCommitTests; | ||
use RefreshDatabase; | ||
|
||
/** | ||
* The current database driver. | ||
* | ||
* @return string | ||
*/ | ||
protected $driver; | ||
|
||
protected function setUp(): void | ||
{ | ||
$this->beforeApplicationDestroyed(function () { | ||
foreach (array_keys($this->app['db']->getConnections()) as $name) { | ||
$this->app['db']->purge($name); | ||
} | ||
}); | ||
|
||
parent::setUp(); | ||
} | ||
|
||
protected function getEnvironmentSetUp($app) | ||
{ | ||
$connection = $app['config']->get('database.default'); | ||
|
||
$this->driver = $app['config']->get("database.connections.$connection.driver"); | ||
} | ||
} |