Skip to content

Commit d2785ba

Browse files
author
Andrey Helldar
authored
Merge pull request #7 from andrey-helldar/features/transactions
Added automatic wrapping of the calling method in a transaction
2 parents da29fbb + 056b1de commit d2785ba

File tree

8 files changed

+253
-18
lines changed

8 files changed

+253
-18
lines changed

README.md

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
* [Generating actions](#generating-actions)
2727
* [Running Actions](#running-actions)
2828
* [Forcing Actions To Run In Production](#forcing-actions-to-run-in-production)
29-
* [Execution every time](#execution-every-time)
29+
* [Execution Every Time](#execution-every-time)
30+
* [Database Transactions](#database-transactions)
3031
* [Rolling Back Actions](#rolling-back-actions)
3132
* [Roll Back & Action Using A Single Command](#roll-back--action-using-a-single-command)
3233
* [Actions Status](#actions-status)
@@ -110,7 +111,7 @@ database, you will be prompted for confirmation before the commands are executed
110111
php artisan migrate:actions --force
111112
```
112113

113-
#### Execution every time
114+
#### Execution Every Time
114115

115116
In some cases, you need to call the code every time you deploy the application. For example, to call reindexing.
116117

@@ -121,14 +122,6 @@ use Helldar\LaravelActions\Support\Actionable;
121122

122123
class Reindex extends Actionable
123124
{
124-
/**
125-
* Determines the type of launch of the action.
126-
*
127-
* If true, then it will be executed once.
128-
* If false, then the action will run every time the `migrate:actions` command is invoked.
129-
*
130-
* @var bool
131-
*/
132125
protected $once = false;
133126

134127
public function up(): void
@@ -148,6 +141,39 @@ If the value is `$once = false`, the `up` method will be called every time the `
148141
In this case, information about it will not be written to the `migration_actions` table and, therefore, the `down` method will not be called when the rollback
149142
command is called.
150143

144+
#### Database Transactions
145+
146+
In some cases, it becomes necessary to undo previously performed actions in the database. For example, when code execution throws an error. To do this, the code
147+
must be wrapped in a transaction.
148+
149+
By setting the `$transactions = true` parameter, you will ensure that your code is wrapped in a transaction without having to manually call
150+
the `DB::transaction()` method. This will reduce the time it takes to create the action.
151+
152+
```php
153+
use Helldar\LaravelActions\Support\Actionable;
154+
155+
class AddSomeData extends Actionable
156+
{
157+
protected $transactions = true;
158+
159+
public function up(): void
160+
{
161+
// ...
162+
163+
$post = Post::create([
164+
'title' => 'Random Title'
165+
]);
166+
167+
$post->tags()->sync($ids);
168+
}
169+
170+
public function down(): void
171+
{
172+
//
173+
}
174+
}
175+
```
176+
151177
### Rolling Back Actions
152178

153179
To roll back the latest action operation, you may use the `rollback` command. This command rolls back the last "batch" of actions, which may include multiple

src/Support/Actionable.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ abstract class Actionable extends Migration implements Contract
1717
*/
1818
protected $once = true;
1919

20+
/**
21+
* Determines a call to database transactions.
22+
*
23+
* By default, false.
24+
*
25+
* @var bool
26+
*/
27+
protected $transactions = false;
28+
2029
/**
2130
* Determines the type of launch of the action.
2231
*
@@ -29,4 +38,14 @@ public function isOnce(): bool
2938
{
3039
return $this->once;
3140
}
41+
42+
/**
43+
* Determines a call to database transactions.
44+
*
45+
* @return bool
46+
*/
47+
public function enabledTransactions(): bool
48+
{
49+
return $this->transactions;
50+
}
3251
}

src/Support/Migrator.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Helldar\LaravelActions\Traits\Infoable;
66
use Illuminate\Database\Migrations\Migrator as BaseMigrator;
7+
use Illuminate\Support\Facades\DB;
78

89
final class Migrator extends BaseMigrator
910
{
@@ -58,6 +59,25 @@ protected function runUp($file, $batch, $pretend)
5859
$this->note("<info>Migrated:</info> {$name} ({$runTime}ms)");
5960
}
6061

62+
/**
63+
* Starts the execution of code, starting database transactions, if necessary.
64+
*
65+
* @param object $migration
66+
* @param string $method
67+
*/
68+
protected function runMigration($migration, $method)
69+
{
70+
if ($this->enabledTransactions($migration)) {
71+
DB::transaction(function () use ($migration, $method) {
72+
parent::runMigration($migration, $method);
73+
});
74+
75+
return;
76+
}
77+
78+
parent::runMigration($migration, $method);
79+
}
80+
6181
/**
6282
* Whether it is necessary to record information about the execution in the database.
6383
*
@@ -69,4 +89,16 @@ protected function allowLogging($migration): bool
6989
{
7090
return $migration->isOnce();
7191
}
92+
93+
/**
94+
* Whether it is necessary to call database transactions at runtime.
95+
*
96+
* @param object $migration
97+
*
98+
* @return bool
99+
*/
100+
protected function enabledTransactions($migration): bool
101+
{
102+
return $migration->enabledTransactions();
103+
}
72104
}

tests/Commands/MigrateTest.php

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Tests\Commands;
44

5+
use Exception;
56
use Tests\TestCase;
67

78
final class MigrateTest extends TestCase
@@ -22,7 +23,7 @@ public function testMigrationCommand()
2223
$this->assertDatabaseMigrationHas($this->table, 'test_migration');
2324
}
2425

25-
public function testEveryTimeExecution()
26+
public function testOnce()
2627
{
2728
$this->copyFiles();
2829

@@ -32,27 +33,69 @@ public function testEveryTimeExecution()
3233

3334
$this->assertDatabaseCount($table, 0);
3435
$this->assertDatabaseCount($this->table, 0);
35-
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
36+
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
3637
$this->artisan('migrate:actions')->run();
3738

3839
$this->assertDatabaseCount($table, 1);
3940
$this->assertDatabaseCount($this->table, 1);
40-
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
41+
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
4142
$this->artisan('migrate:actions')->run();
4243

4344
$this->assertDatabaseCount($table, 2);
4445
$this->assertDatabaseCount($this->table, 1);
45-
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
46+
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
4647
$this->artisan('migrate:actions')->run();
4748

4849
$this->assertDatabaseCount($table, 3);
4950
$this->assertDatabaseCount($this->table, 1);
50-
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
51+
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
5152
$this->artisan('migrate:actions')->run();
5253

5354
$this->assertDatabaseCount($table, 4);
5455
$this->assertDatabaseCount($this->table, 1);
55-
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
56+
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
57+
}
58+
59+
public function testSuccessTransaction()
60+
{
61+
$this->copySuccessTransaction();
62+
63+
$table = 'transactions';
64+
65+
$this->artisan('migrate:actions:install')->run();
66+
67+
$this->assertDatabaseCount($table, 0);
68+
$this->assertDatabaseCount($this->table, 0);
69+
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
70+
$this->artisan('migrate:actions')->run();
71+
72+
$this->assertDatabaseCount($table, 3);
73+
$this->assertDatabaseCount($this->table, 1);
74+
$this->assertDatabaseMigrationHas($this->table, $table);
75+
}
76+
77+
public function testFailedTransaction()
78+
{
79+
$this->copyFailedTransaction();
80+
81+
$table = 'transactions';
82+
83+
$this->artisan('migrate:actions:install')->run();
84+
85+
$this->assertDatabaseCount($table, 0);
86+
$this->assertDatabaseCount($this->table, 0);
87+
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
88+
89+
try {
90+
$this->artisan('migrate:actions')->run();
91+
} catch (Exception $e) {
92+
$this->assertSame(Exception::class, get_class($e));
93+
$this->assertSame('Random message', $e->getMessage());
94+
}
95+
96+
$this->assertDatabaseCount($table, 0);
97+
$this->assertDatabaseCount($this->table, 0);
98+
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
5699
}
57100

58101
public function testMigrationNotFound()

tests/Concerns/Files.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,40 @@ trait Files
99
protected function freshFiles(): void
1010
{
1111
File::deleteDirectory(
12-
database_path('actions')
12+
$this->targetDirectory()
1313
);
1414
}
1515

1616
protected function copyFiles(): void
1717
{
1818
File::copyDirectory(
1919
__DIR__ . '/../fixtures/actions',
20-
database_path('actions')
20+
$this->targetDirectory()
2121
);
2222
}
23+
24+
protected function copySuccessTransaction(): void
25+
{
26+
File::copy(
27+
__DIR__ . '/../fixtures/stubs/2021_02_15_124237_test_success_transactions.stub',
28+
$this->targetDirectory('2021_02_15_124237_test_success_transactions.php')
29+
);
30+
}
31+
32+
protected function copyFailedTransaction(): void
33+
{
34+
File::copy(
35+
__DIR__ . '/../fixtures/stubs/2021_02_15_124852_test_failed_transactions.stub',
36+
$this->targetDirectory('2021_02_15_124852_test_failed_transactions.php')
37+
);
38+
}
39+
40+
protected function targetDirectory(string $path = null): string
41+
{
42+
$dir = database_path('actions');
43+
44+
File::ensureDirectoryExists($dir);
45+
46+
return rtrim($dir, '/\\') . '/' . ltrim($path, '/\\');
47+
}
2348
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
final class CreateTransactionsTable extends Migration
8+
{
9+
public function up()
10+
{
11+
Schema::create('transactions', function (Blueprint $table) {
12+
$table->uuid('value');
13+
});
14+
}
15+
16+
public function down()
17+
{
18+
Schema::dropIfExists('transactions');
19+
}
20+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
use Helldar\LaravelActions\Support\Actionable;
4+
use Illuminate\Support\Facades\DB;
5+
use Ramsey\Uuid\Uuid;
6+
7+
final class TestSuccessTransactions extends Actionable
8+
{
9+
protected $transactions = true;
10+
11+
public function up(): void
12+
{
13+
$this->table()->insert([
14+
$this->value(),
15+
$this->value(),
16+
$this->value(),
17+
]);
18+
}
19+
20+
public function down(): void
21+
{
22+
// nothing
23+
}
24+
25+
protected function table()
26+
{
27+
return DB::table('transactions');
28+
}
29+
30+
protected function value(): array
31+
{
32+
return ['value' => Uuid::uuid4()];
33+
}
34+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
use Helldar\LaravelActions\Support\Actionable;
4+
use Illuminate\Support\Facades\DB;
5+
use Ramsey\Uuid\Uuid;
6+
7+
final class TestFailedTransactions extends Actionable
8+
{
9+
protected $transactions = true;
10+
11+
public function up(): void
12+
{
13+
$this->table()->insert([
14+
$this->value(),
15+
$this->value(),
16+
$this->value(),
17+
]);
18+
19+
throw new Exception('Random message');
20+
}
21+
22+
public function down(): void
23+
{
24+
// nothing
25+
}
26+
27+
protected function table()
28+
{
29+
return DB::table('transactions');
30+
}
31+
32+
protected function value(): array
33+
{
34+
return ['value' => Uuid::uuid4()];
35+
}
36+
}

0 commit comments

Comments
 (0)