Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 36 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
* [Generating actions](#generating-actions)
* [Running Actions](#running-actions)
* [Forcing Actions To Run In Production](#forcing-actions-to-run-in-production)
* [Execution every time](#execution-every-time)
* [Execution Every Time](#execution-every-time)
* [Database Transactions](#database-transactions)
* [Rolling Back Actions](#rolling-back-actions)
* [Roll Back & Action Using A Single Command](#roll-back--action-using-a-single-command)
* [Actions Status](#actions-status)
Expand Down Expand Up @@ -110,7 +111,7 @@ database, you will be prompted for confirmation before the commands are executed
php artisan migrate:actions --force
```

#### Execution every time
#### Execution Every Time

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

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

class Reindex extends Actionable
{
/**
* Determines the type of launch of the action.
*
* If true, then it will be executed once.
* If false, then the action will run every time the `migrate:actions` command is invoked.
*
* @var bool
*/
protected $once = false;

public function up(): void
Expand All @@ -148,6 +141,39 @@ If the value is `$once = false`, the `up` method will be called every time the `
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
command is called.

#### Database Transactions

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
must be wrapped in a transaction.

By setting the `$transactions = true` parameter, you will ensure that your code is wrapped in a transaction without having to manually call
the `DB::transaction()` method. This will reduce the time it takes to create the action.

```php
use Helldar\LaravelActions\Support\Actionable;

class AddSomeData extends Actionable
{
protected $transactions = true;

public function up(): void
{
// ...

$post = Post::create([
'title' => 'Random Title'
]);

$post->tags()->sync($ids);
}

public function down(): void
{
//
}
}
```

### Rolling Back Actions

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
Expand Down
19 changes: 19 additions & 0 deletions src/Support/Actionable.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ abstract class Actionable extends Migration implements Contract
*/
protected $once = true;

/**
* Determines a call to database transactions.
*
* By default, false.
*
* @var bool
*/
protected $transactions = false;

/**
* Determines the type of launch of the action.
*
Expand All @@ -29,4 +38,14 @@ public function isOnce(): bool
{
return $this->once;
}

/**
* Determines a call to database transactions.
*
* @return bool
*/
public function enabledTransactions(): bool
{
return $this->transactions;
}
}
32 changes: 32 additions & 0 deletions src/Support/Migrator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Helldar\LaravelActions\Traits\Infoable;
use Illuminate\Database\Migrations\Migrator as BaseMigrator;
use Illuminate\Support\Facades\DB;

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

/**
* Starts the execution of code, starting database transactions, if necessary.
*
* @param object $migration
* @param string $method
*/
protected function runMigration($migration, $method)
{
if ($this->enabledTransactions($migration)) {
DB::transaction(function () use ($migration, $method) {
parent::runMigration($migration, $method);
});

return;
}

parent::runMigration($migration, $method);
}

/**
* Whether it is necessary to record information about the execution in the database.
*
Expand All @@ -69,4 +89,16 @@ protected function allowLogging($migration): bool
{
return $migration->isOnce();
}

/**
* Whether it is necessary to call database transactions at runtime.
*
* @param object $migration
*
* @return bool
*/
protected function enabledTransactions($migration): bool
{
return $migration->enabledTransactions();
}
}
55 changes: 49 additions & 6 deletions tests/Commands/MigrateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Tests\Commands;

use Exception;
use Tests\TestCase;

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

public function testEveryTimeExecution()
public function testOnce()
{
$this->copyFiles();

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

$this->assertDatabaseCount($table, 0);
$this->assertDatabaseCount($this->table, 0);
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
$this->artisan('migrate:actions')->run();

$this->assertDatabaseCount($table, 1);
$this->assertDatabaseCount($this->table, 1);
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
$this->artisan('migrate:actions')->run();

$this->assertDatabaseCount($table, 2);
$this->assertDatabaseCount($this->table, 1);
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
$this->artisan('migrate:actions')->run();

$this->assertDatabaseCount($table, 3);
$this->assertDatabaseCount($this->table, 1);
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
$this->artisan('migrate:actions')->run();

$this->assertDatabaseCount($table, 4);
$this->assertDatabaseCount($this->table, 1);
$this->assertDatabaseMigrationDoesntLike($this->table, 'every_time');
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
}

public function testSuccessTransaction()
{
$this->copySuccessTransaction();

$table = 'transactions';

$this->artisan('migrate:actions:install')->run();

$this->assertDatabaseCount($table, 0);
$this->assertDatabaseCount($this->table, 0);
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
$this->artisan('migrate:actions')->run();

$this->assertDatabaseCount($table, 3);
$this->assertDatabaseCount($this->table, 1);
$this->assertDatabaseMigrationHas($this->table, $table);
}

public function testFailedTransaction()
{
$this->copyFailedTransaction();

$table = 'transactions';

$this->artisan('migrate:actions:install')->run();

$this->assertDatabaseCount($table, 0);
$this->assertDatabaseCount($this->table, 0);
$this->assertDatabaseMigrationDoesntLike($this->table, $table);

try {
$this->artisan('migrate:actions')->run();
} catch (Exception $e) {
$this->assertSame(Exception::class, get_class($e));
$this->assertSame('Random message', $e->getMessage());
}

$this->assertDatabaseCount($table, 0);
$this->assertDatabaseCount($this->table, 0);
$this->assertDatabaseMigrationDoesntLike($this->table, $table);
}

public function testMigrationNotFound()
Expand Down
29 changes: 27 additions & 2 deletions tests/Concerns/Files.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,40 @@ trait Files
protected function freshFiles(): void
{
File::deleteDirectory(
database_path('actions')
$this->targetDirectory()
);
}

protected function copyFiles(): void
{
File::copyDirectory(
__DIR__ . '/../fixtures/actions',
database_path('actions')
$this->targetDirectory()
);
}

protected function copySuccessTransaction(): void
{
File::copy(
__DIR__ . '/../fixtures/stubs/2021_02_15_124237_test_success_transactions.stub',
$this->targetDirectory('2021_02_15_124237_test_success_transactions.php')
);
}

protected function copyFailedTransaction(): void
{
File::copy(
__DIR__ . '/../fixtures/stubs/2021_02_15_124852_test_failed_transactions.stub',
$this->targetDirectory('2021_02_15_124852_test_failed_transactions.php')
);
}

protected function targetDirectory(string $path = null): string
{
$dir = database_path('actions');

File::ensureDirectoryExists($dir);

return rtrim($dir, '/\\') . '/' . ltrim($path, '/\\');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

final class CreateTransactionsTable extends Migration
{
public function up()
{
Schema::create('transactions', function (Blueprint $table) {
$table->uuid('value');
});
}

public function down()
{
Schema::dropIfExists('transactions');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

use Helldar\LaravelActions\Support\Actionable;
use Illuminate\Support\Facades\DB;
use Ramsey\Uuid\Uuid;

final class TestSuccessTransactions extends Actionable
{
protected $transactions = true;

public function up(): void
{
$this->table()->insert([
$this->value(),
$this->value(),
$this->value(),
]);
}

public function down(): void
{
// nothing
}

protected function table()
{
return DB::table('transactions');
}

protected function value(): array
{
return ['value' => Uuid::uuid4()];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

use Helldar\LaravelActions\Support\Actionable;
use Illuminate\Support\Facades\DB;
use Ramsey\Uuid\Uuid;

final class TestFailedTransactions extends Actionable
{
protected $transactions = true;

public function up(): void
{
$this->table()->insert([
$this->value(),
$this->value(),
$this->value(),
]);

throw new Exception('Random message');
}

public function down(): void
{
// nothing
}

protected function table()
{
return DB::table('transactions');
}

protected function value(): array
{
return ['value' => Uuid::uuid4()];
}
}