Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 1 addition & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
- name: Install dependencies
run: |
composer config allow-plugins.pestphp/pest-plugin true
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" pestphp/pest --no-interaction --no-update
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
- name: Execute tests
run: XDEBUG_MODE=coverage php vendor/bin/pest --coverage --min=100
12 changes: 8 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
"justbetter/laravel-magento-webhooks": "^2.1"
},
"require-dev": {
"laravel/pint": "^1.16",
"larastan/larastan": "^2.5",
"laravel/pint": "^1.16",
"orchestra/testbench": "^9.0",
"pestphp/pest": "^2.0",
"phpstan/phpstan-mockery": "^1.1",
"phpunit/phpunit": "^10.0",
"orchestra/testbench": "^9.0"
"phpunit/phpunit": "^10.0"
},
"authors": [
{
Expand Down Expand Up @@ -51,7 +52,10 @@
"fix-style": "pint"
},
"config": {
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"extra": {
"laravel": {
Expand Down
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;

return new class extends Migration
{
public function up(): void
{
Schema::table('magento_bulk_requests', function (Blueprint $table): void {
$table->string('method')->after('store_code');
});
}

public function down(): void
{
Schema::dropColumns('magento_bulk_requests', ['method']);
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

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

return new class extends Migration
{
public function up(): void
{
Schema::table('magento_bulk_requests', function (Blueprint $table): void {
$table->unsignedBigInteger('retry_of')->after('id')->nullable();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice if you could add a foreign key constraint that will set the value to NULL if the relation has been removed.


$table->foreign('retry_of')->references('id')->on('magento_bulk_requests')->onDelete('set null');
});
}

public function down(): void
{
Schema::dropColumns('magento_bulk_requests', ['retry_of']);
}
};
66 changes: 66 additions & 0 deletions src/Actions/RetryBulkRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace JustBetter\MagentoAsync\Actions;

use Illuminate\Database\Eloquent\Model;
use JustBetter\MagentoAsync\Client\MagentoAsync;
use JustBetter\MagentoAsync\Contracts\RetriesBulkRequest;
use JustBetter\MagentoAsync\Enums\OperationStatus;
use JustBetter\MagentoAsync\Exceptions\InvalidMethodException;
use JustBetter\MagentoAsync\Models\BulkOperation;
use JustBetter\MagentoAsync\Models\BulkRequest;
use JustBetter\MagentoClient\Client\Magento;

class RetryBulkRequest implements RetriesBulkRequest
{
public function __construct(protected MagentoAsync $client) {}

public function retry(BulkRequest $bulkRequest, bool $onlyFailed): ?BulkRequest
{
/** @var array<int, mixed> $payload */
$payload = [];
/** @var array<int, ?Model> $subjects */
$subjects = [];

$operations = $bulkRequest->operations;

foreach ($bulkRequest->request as $index => $request) {

/** @var BulkOperation $operation */
$operation = $operations->where('operation_id', '=', $index)->firstOrFail();

if ($onlyFailed && ! in_array($operation->status, OperationStatus::failedStatuses())) {
continue;
}

$payload[] = $request;
$subjects[] = $operation->subject;
}

if ($payload === []) {
return null;
}

$pendingRequest = $this->client
->configure(fn (Magento $client): Magento => $client->store($bulkRequest->store_code))
->subjects($subjects);

$retry = match ($bulkRequest->method) {
'POST' => $pendingRequest->postBulk($bulkRequest->path, $payload),
'PUT' => $pendingRequest->putBulk($bulkRequest->path, $payload),
'DELETE' => $pendingRequest->deleteBulk($bulkRequest->path, $payload),
default => throw new InvalidMethodException('Unsupported method "'.$bulkRequest->method.'"'),
};

if ($retry !== null) {
$bulkRequest->retries()->save($retry);
}

return $retry;
}

public static function bind(): void
{
app()->singleton(RetriesBulkRequest::class, static::class);
}
}
38 changes: 20 additions & 18 deletions src/Client/MagentoAsync.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class MagentoAsync

protected ?Model $subject = null;

/** @var array<int, Model> */
/** @var array<int, ?Model> */
protected array $subjects = [];

public function __construct(
Expand Down Expand Up @@ -70,68 +70,69 @@ public function subject(Model $subject): static
return $this;
}

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

return $this;
}

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

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

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

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

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

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

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

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

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

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

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

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

/** @param array<mixed, mixed> $data */
/** @param array<mixed, mixed> $data */
public function processResponse(
Response $response,
string $method,
string $path,
array $data = [],
bool $bulk = false
bool $bulk = false,
): BulkRequest {
$response->throw();

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

$response = $response->json(null, []);
$responseData = $response->json(null, []);

/** @var BulkRequest $bulkRequest */
$bulkRequest = BulkRequest::query()->create([
'magento_connection' => $this->magento->connection,
'store_code' => $this->magento->storeCode ?? 'all',
'method' => $method,
'path' => $path,
'bulk_uuid' => $bulkUuid,
'request' => $data,
'response' => $response,
'response' => $responseData,
]);

foreach ($requestItems as $index => $requestItem) {
Expand Down
38 changes: 38 additions & 0 deletions src/Commands/RetryBulkRequestCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace JustBetter\MagentoAsync\Commands;

use Illuminate\Console\Command;
use JustBetter\MagentoAsync\Contracts\RetriesBulkRequest;
use JustBetter\MagentoAsync\Models\BulkRequest;

class RetryBulkRequestCommand extends Command
{
protected $signature = 'magento:async:retry-bulk-request {id} {--only-failed=true}';

protected $description = 'Retry bulk request';

public function handle(RetriesBulkRequest $contract): int
{
/** @var int $id */
$id = $this->argument('id');

/** @var bool $onlyFailed */
$onlyFailed = $this->option('only-failed');

/** @var BulkRequest $request */
$request = BulkRequest::query()->findOrFail($id);

$bulkRequest = $contract->retry($request, $onlyFailed);

if ($bulkRequest === null) {
$this->error('Failed to retry bulk request');

return static::FAILURE;
}

$this->info('Retried with bulk uuid "'.$bulkRequest->bulk_uuid.'"');

return static::SUCCESS;
}
}
10 changes: 10 additions & 0 deletions src/Contracts/RetriesBulkRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace JustBetter\MagentoAsync\Contracts;

use JustBetter\MagentoAsync\Models\BulkRequest;

interface RetriesBulkRequest
{
public function retry(BulkRequest $bulkRequest, bool $onlyFailed): ?BulkRequest;
}
9 changes: 9 additions & 0 deletions src/Enums/OperationStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,13 @@ enum OperationStatus: int
case NotRetriablyFailed = 3;
case Open = 4;
case Rejected = 5;

/** @return array<int, OperationStatus> */
public static function failedStatuses(): array
{
return [
OperationStatus::RetriablyFailed,
OperationStatus::NotRetriablyFailed,
];
}
}
7 changes: 7 additions & 0 deletions src/Exceptions/InvalidMethodException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace JustBetter\MagentoAsync\Exceptions;

use Exception;

class InvalidMethodException extends Exception {}
17 changes: 17 additions & 0 deletions src/Models/BulkRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Carbon;

/**
* @property int $id
* @property ?int $retry_of
* @property string $magento_connection
* @property string $store_code
* @property string $method
* @property string $path
* @property string $bulk_uuid
* @property array $request
Expand All @@ -19,6 +22,8 @@
* @property ?Carbon $created_at
* @property ?Carbon $updated_at
* @property Collection<int, BulkOperation> $operations
* @property Collection<int, BulkRequest> $retries
* @property ?BulkRequest $retryOf
*/
class BulkRequest extends Model
{
Expand All @@ -37,4 +42,16 @@ public function operations(): HasMany
{
return $this->hasMany(BulkOperation::class);
}

/** @return HasMany<BulkRequest> */
public function retries(): HasMany
{
return $this->hasMany(BulkRequest::class, 'retry_of', 'id');
}

/** @return BelongsTo<BulkRequest, BulkRequest> */
public function retryOf(): BelongsTo
{
return $this->belongsTo(BulkRequest::class, 'retry_of', 'id');
}
}
Loading