Skip to content

Commit e8ddae8

Browse files
authored
Merge pull request #4 from justbetter/feature/retry
Implement retries
2 parents b128be4 + 62c1f60 commit e8ddae8

23 files changed

+492
-23
lines changed

.github/workflows/coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
- name: Install dependencies
3333
run: |
3434
composer config allow-plugins.pestphp/pest-plugin true
35-
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" pestphp/pest --no-interaction --no-update
35+
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
3636
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
3737
- name: Execute tests
3838
run: XDEBUG_MODE=coverage php vendor/bin/pest --coverage --min=100

composer.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@
1111
"justbetter/laravel-magento-webhooks": "^2.1"
1212
},
1313
"require-dev": {
14-
"laravel/pint": "^1.16",
1514
"larastan/larastan": "^2.5",
15+
"laravel/pint": "^1.16",
16+
"orchestra/testbench": "^9.0",
17+
"pestphp/pest": "^2.0",
1618
"phpstan/phpstan-mockery": "^1.1",
17-
"phpunit/phpunit": "^10.0",
18-
"orchestra/testbench": "^9.0"
19+
"phpunit/phpunit": "^10.0"
1920
},
2021
"authors": [
2122
{
@@ -51,7 +52,10 @@
5152
"fix-style": "pint"
5253
},
5354
"config": {
54-
"sort-packages": true
55+
"sort-packages": true,
56+
"allow-plugins": {
57+
"pestphp/pest-plugin": true
58+
}
5559
},
5660
"extra": {
5761
"laravel": {
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+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::table('magento_bulk_requests', function (Blueprint $table): void {
12+
$table->string('method')->after('store_code');
13+
});
14+
}
15+
16+
public function down(): void
17+
{
18+
Schema::dropColumns('magento_bulk_requests', ['method']);
19+
}
20+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
public function up(): void
10+
{
11+
Schema::table('magento_bulk_requests', function (Blueprint $table): void {
12+
$table->unsignedBigInteger('retry_of')->after('id')->nullable();
13+
14+
$table->foreign('retry_of')->references('id')->on('magento_bulk_requests')->onDelete('set null');
15+
});
16+
}
17+
18+
public function down(): void
19+
{
20+
Schema::dropColumns('magento_bulk_requests', ['retry_of']);
21+
}
22+
};

src/Actions/RetryBulkRequest.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace JustBetter\MagentoAsync\Actions;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use JustBetter\MagentoAsync\Client\MagentoAsync;
7+
use JustBetter\MagentoAsync\Contracts\RetriesBulkRequest;
8+
use JustBetter\MagentoAsync\Enums\OperationStatus;
9+
use JustBetter\MagentoAsync\Exceptions\InvalidMethodException;
10+
use JustBetter\MagentoAsync\Models\BulkOperation;
11+
use JustBetter\MagentoAsync\Models\BulkRequest;
12+
use JustBetter\MagentoClient\Client\Magento;
13+
14+
class RetryBulkRequest implements RetriesBulkRequest
15+
{
16+
public function __construct(protected MagentoAsync $client) {}
17+
18+
public function retry(BulkRequest $bulkRequest, bool $onlyFailed): ?BulkRequest
19+
{
20+
/** @var array<int, mixed> $payload */
21+
$payload = [];
22+
/** @var array<int, ?Model> $subjects */
23+
$subjects = [];
24+
25+
$operations = $bulkRequest->operations;
26+
27+
foreach ($bulkRequest->request as $index => $request) {
28+
29+
/** @var BulkOperation $operation */
30+
$operation = $operations->where('operation_id', '=', $index)->firstOrFail();
31+
32+
if ($onlyFailed && ! in_array($operation->status, OperationStatus::failedStatuses())) {
33+
continue;
34+
}
35+
36+
$payload[] = $request;
37+
$subjects[] = $operation->subject;
38+
}
39+
40+
if ($payload === []) {
41+
return null;
42+
}
43+
44+
$pendingRequest = $this->client
45+
->configure(fn (Magento $client): Magento => $client->store($bulkRequest->store_code))
46+
->subjects($subjects);
47+
48+
$retry = match ($bulkRequest->method) {
49+
'POST' => $pendingRequest->postBulk($bulkRequest->path, $payload),
50+
'PUT' => $pendingRequest->putBulk($bulkRequest->path, $payload),
51+
'DELETE' => $pendingRequest->deleteBulk($bulkRequest->path, $payload),
52+
default => throw new InvalidMethodException('Unsupported method "'.$bulkRequest->method.'"'),
53+
};
54+
55+
if ($retry !== null) {
56+
$bulkRequest->retries()->save($retry);
57+
}
58+
59+
return $retry;
60+
}
61+
62+
public static function bind(): void
63+
{
64+
app()->singleton(RetriesBulkRequest::class, static::class);
65+
}
66+
}

src/Client/MagentoAsync.php

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class MagentoAsync
2121

2222
protected ?Model $subject = null;
2323

24-
/** @var array<int, Model> */
24+
/** @var array<int, ?Model> */
2525
protected array $subjects = [];
2626

2727
public function __construct(
@@ -70,68 +70,69 @@ public function subject(Model $subject): static
7070
return $this;
7171
}
7272

73-
/** @param array<int, Model> $subjects */
73+
/** @param array<int, ?Model> $subjects */
7474
public function subjects(array $subjects): static
7575
{
7676
$this->subjects = $subjects;
7777

7878
return $this;
7979
}
8080

81-
/** @param array<mixed, mixed> $data */
81+
/** @param array<mixed, mixed> $data */
8282
public function post(string $path, array $data = []): BulkRequest
8383
{
8484
$response = $this->magento->postAsync($path, $data);
8585

86-
return $this->processResponse($response, $path, $data);
86+
return $this->processResponse($response, 'POST', $path, $data);
8787
}
8888

89-
/** @param array<mixed, mixed> $data */
89+
/** @param array<mixed, mixed> $data */
9090
public function postBulk(string $path, array $data = []): BulkRequest
9191
{
9292
$response = $this->magento->postBulk($path, $data);
9393

94-
return $this->processResponse($response, $path, $data, true);
94+
return $this->processResponse($response, 'POST', $path, $data, true);
9595
}
9696

97-
/** @param array<mixed, mixed> $data */
97+
/** @param array<mixed, mixed> $data */
9898
public function put(string $path, array $data = []): BulkRequest
9999
{
100100
$response = $this->magento->putAsync($path, $data);
101101

102-
return $this->processResponse($response, $path, $data);
102+
return $this->processResponse($response, 'PUT', $path, $data);
103103
}
104104

105-
/** @param array<mixed, mixed> $data */
105+
/** @param array<mixed, mixed> $data */
106106
public function putBulk(string $path, array $data = []): BulkRequest
107107
{
108108
$response = $this->magento->putBulk($path, $data);
109109

110-
return $this->processResponse($response, $path, $data, true);
110+
return $this->processResponse($response, 'PUT', $path, $data, true);
111111
}
112112

113-
/** @param array<mixed, mixed> $data */
113+
/** @param array<mixed, mixed> $data */
114114
public function delete(string $path, array $data = []): BulkRequest
115115
{
116116
$response = $this->magento->deleteAsync($path, $data);
117117

118-
return $this->processResponse($response, $path, $data);
118+
return $this->processResponse($response, 'DELETE', $path, $data);
119119
}
120120

121-
/** @param array<mixed, mixed> $data */
121+
/** @param array<mixed, mixed> $data */
122122
public function deleteBulk(string $path, array $data = []): BulkRequest
123123
{
124124
$response = $this->magento->deleteBulk($path, $data);
125125

126-
return $this->processResponse($response, $path, $data, true);
126+
return $this->processResponse($response, 'DELETE', $path, $data, true);
127127
}
128128

129-
/** @param array<mixed, mixed> $data */
129+
/** @param array<mixed, mixed> $data */
130130
public function processResponse(
131131
Response $response,
132+
string $method,
132133
string $path,
133134
array $data = [],
134-
bool $bulk = false
135+
bool $bulk = false,
135136
): BulkRequest {
136137
$response->throw();
137138

@@ -141,16 +142,17 @@ public function processResponse(
141142
/** @var array<int, array<string, mixed>> $requestItems */
142143
$requestItems = $response->json('request_items', []);
143144

144-
$response = $response->json(null, []);
145+
$responseData = $response->json(null, []);
145146

146147
/** @var BulkRequest $bulkRequest */
147148
$bulkRequest = BulkRequest::query()->create([
148149
'magento_connection' => $this->magento->connection,
149150
'store_code' => $this->magento->storeCode ?? 'all',
151+
'method' => $method,
150152
'path' => $path,
151153
'bulk_uuid' => $bulkUuid,
152154
'request' => $data,
153-
'response' => $response,
155+
'response' => $responseData,
154156
]);
155157

156158
foreach ($requestItems as $index => $requestItem) {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace JustBetter\MagentoAsync\Commands;
4+
5+
use Illuminate\Console\Command;
6+
use JustBetter\MagentoAsync\Contracts\RetriesBulkRequest;
7+
use JustBetter\MagentoAsync\Models\BulkRequest;
8+
9+
class RetryBulkRequestCommand extends Command
10+
{
11+
protected $signature = 'magento:async:retry-bulk-request {id} {--only-failed}';
12+
13+
protected $description = 'Retry bulk request';
14+
15+
public function handle(RetriesBulkRequest $contract): int
16+
{
17+
/** @var int $id */
18+
$id = $this->argument('id');
19+
20+
/** @var bool $onlyFailed */
21+
$onlyFailed = $this->option('only-failed');
22+
23+
/** @var BulkRequest $request */
24+
$request = BulkRequest::query()->findOrFail($id);
25+
26+
$bulkRequest = $contract->retry($request, $onlyFailed);
27+
28+
if ($bulkRequest === null) {
29+
$this->error('Failed to retry bulk request');
30+
31+
return static::FAILURE;
32+
}
33+
34+
$this->info('Retried with bulk uuid "'.$bulkRequest->bulk_uuid.'"');
35+
36+
return static::SUCCESS;
37+
}
38+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace JustBetter\MagentoAsync\Contracts;
4+
5+
use JustBetter\MagentoAsync\Models\BulkRequest;
6+
7+
interface RetriesBulkRequest
8+
{
9+
public function retry(BulkRequest $bulkRequest, bool $onlyFailed): ?BulkRequest;
10+
}

src/Enums/OperationStatus.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,13 @@ enum OperationStatus: int
99
case NotRetriablyFailed = 3;
1010
case Open = 4;
1111
case Rejected = 5;
12+
13+
/** @return array<int, OperationStatus> */
14+
public static function failedStatuses(): array
15+
{
16+
return [
17+
OperationStatus::RetriablyFailed,
18+
OperationStatus::NotRetriablyFailed,
19+
];
20+
}
1221
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace JustBetter\MagentoAsync\Exceptions;
4+
5+
use Exception;
6+
7+
class InvalidMethodException extends Exception {}

0 commit comments

Comments
 (0)