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
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: ['8.1', '8.2', '8.3']
php: ['8.1', '8.2', '8.3', '8.4', '8.5']

name: PHP ${{ matrix.php }}

Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,38 @@ $result = $client->importBlogs($blogs);
echo sprintf('Imported: %d, Updated: %d', $result->imported, $result->updated);
```

## Delete

### Delete Products

```php
$result = $client->deleteProducts(['PROD-001', 'PROD-002', 'PROD-003']);

echo sprintf('Deleted: %d, Skipped: %d', $result->deleted, $result->skipped);

if ($result->hasErrors() === true) {
foreach ($result->errors as $error) {
echo sprintf('ID %s: %s', $error['id'], implode(', ', $error['errors']));
}
}
```

### Delete Categories

```php
$result = $client->deleteCategories(['CAT-001', 'CAT-002']);
echo sprintf('Deleted: %d', $result->deleted);
```

### Delete Blogs

```php
$result = $client->deleteBlogs(['BLOG-001', 'BLOG-002']);
echo sprintf('Deleted: %d', $result->deleted);
```

> **Note:** Delete performs a soft-delete.

## Export

### Export Products
Expand Down Expand Up @@ -400,6 +432,9 @@ $name->toArray(); // ['default' => '...', 'cs' => '...', ...]
| `importCategories(array $categories)` | Bulk import categories (max 100) |
| `importParameters(array $parameters)` | Bulk import parameters (max 100) |
| `importBlogs(array $blogs)` | Bulk import blogs (max 100) |
| `deleteProducts(array $ids)` | Bulk delete products (max 100) |
| `deleteCategories(array $ids)` | Bulk delete categories (max 100) |
| `deleteBlogs(array $ids)` | Bulk delete blogs (max 100) |
| `getProducts(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get products page |
| `getCategories(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get categories page |
| `getBlogs(?int $page, ?int $perPage, ?DateTime $lastUpdateFrom, ?bool $isEdited)` | Get blogs page |
Expand All @@ -412,6 +447,7 @@ $name->toArray(); // ['default' => '...', 'cs' => '...', ...]
| Limit | Value |
|------------------------------|--------------|
| Max items per import request | 100 |
| Max items per delete request | 100 |
| Max items per export page | 100 |
| Product/Category ID length | 255 chars |
| Name length | 250 chars |
Expand Down
37 changes: 37 additions & 0 deletions src/DTO/DeleteResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace Pobo\Sdk\DTO;

final class DeleteResult
{
/**
* @param array<array{index: int, id: string, errors: array<string>}> $errors
*/
public function __construct(
public readonly bool $success,
public readonly int $deleted,
public readonly int $skipped,
public readonly array $errors = [],
) {
}

public function hasErrors(): bool
{
return $this->errors !== [];
}

/**
* @param array<string, mixed> $data
*/
public static function fromArray(array $data): self
{
return new self(
success: $data['success'] ?? false,
deleted: $data['deleted'] ?? 0,
skipped: $data['skipped'] ?? 0,
errors: $data['errors'] ?? [],
);
}
}
2 changes: 1 addition & 1 deletion src/Exception/ValidationException.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public static function tooManyItems(int $count, int $max): self
{
return new self(
sprintf('Too many items: %d provided, maximum is %d', $count, $max),
['bulk' => [sprintf('Maximum %d items allowed for bulk import', $max)]]
['bulk' => [sprintf('Maximum %d items allowed per request', $max)]]
);
}

Expand Down
51 changes: 51 additions & 0 deletions src/PoboClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Pobo\Sdk\DTO\Blog;
use Pobo\Sdk\DTO\Category;
use Pobo\Sdk\DTO\DeleteResult;
use Pobo\Sdk\DTO\ImportResult;
use Pobo\Sdk\DTO\PaginatedResponse;
use Pobo\Sdk\DTO\Parameter;
Expand Down Expand Up @@ -140,6 +141,51 @@ public function getBlogs(
return PaginatedResponse::fromArray($response, Blog::class);
}

/**
* @param array<string> $ids
* @throws ValidationException
* @throws ApiException
*/
public function deleteProducts(array $ids): DeleteResult
{
$this->validateBulkSize($ids);

$payload = array_map(fn(string $id) => ['id' => $id], $ids);

$response = $this->request('DELETE', '/api/v2/rest/products', $payload);
return DeleteResult::fromArray($response);
}

/**
* @param array<string> $ids
* @throws ValidationException
* @throws ApiException
*/
public function deleteCategories(array $ids): DeleteResult
{
$this->validateBulkSize($ids);

$payload = array_map(fn(string $id) => ['id' => $id], $ids);

$response = $this->request('DELETE', '/api/v2/rest/categories', $payload);
return DeleteResult::fromArray($response);
}

/**
* @param array<string> $ids
* @throws ValidationException
* @throws ApiException
*/
public function deleteBlogs(array $ids): DeleteResult
{
$this->validateBulkSize($ids);

$payload = array_map(fn(string $id) => ['id' => $id], $ids);

$response = $this->request('DELETE', '/api/v2/rest/blogs', $payload);
return DeleteResult::fromArray($response);
}

/**
* @return \Generator<Product>
* @throws ApiException
Expand Down Expand Up @@ -275,6 +321,11 @@ private function request(string $method, string $endpoint, ?array $data = null):
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
} elseif ($method === 'DELETE') {
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
if ($data !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
}
}

$response = curl_exec($ch);
Expand Down
86 changes: 86 additions & 0 deletions tests/DTO/DeleteResultTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace Pobo\Sdk\Tests\DTO;

use PHPUnit\Framework\TestCase;
use Pobo\Sdk\DTO\DeleteResult;

final class DeleteResultTest extends TestCase
{
public function testFromArraySuccess(): void
{
$data = [
'success' => true,
'deleted' => 3,
'skipped' => 1,
'errors' => [],
];

$result = DeleteResult::fromArray($data);

$this->assertTrue($result->success);
$this->assertSame(3, $result->deleted);
$this->assertSame(1, $result->skipped);
$this->assertSame([], $result->errors);
$this->assertFalse($result->hasErrors());
}

public function testFromArrayWithErrors(): void
{
$data = [
'success' => true,
'deleted' => 2,
'skipped' => 1,
'errors' => [
[
'index' => 2,
'id' => 'PROD-999',
'errors' => ['Product not found'],
],
],
];

$result = DeleteResult::fromArray($data);

$this->assertTrue($result->hasErrors());
$this->assertCount(1, $result->errors);
$this->assertSame(2, $result->errors[0]['index']);
$this->assertSame('PROD-999', $result->errors[0]['id']);
}

public function testFromArrayWithDefaults(): void
{
$result = DeleteResult::fromArray([]);

$this->assertFalse($result->success);
$this->assertSame(0, $result->deleted);
$this->assertSame(0, $result->skipped);
$this->assertSame([], $result->errors);
}

public function testHasErrorsReturnsFalseForEmptyErrors(): void
{
$result = new DeleteResult(
success: true,
deleted: 1,
skipped: 0,
errors: [],
);

$this->assertFalse($result->hasErrors());
}

public function testHasErrorsReturnsTrueForNonEmptyErrors(): void
{
$result = new DeleteResult(
success: true,
deleted: 0,
skipped: 1,
errors: [['index' => 0, 'id' => 'X', 'errors' => ['Not found']]],
);

$this->assertTrue($result->hasErrors());
}
}
81 changes: 81 additions & 0 deletions tests/PoboClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use PHPUnit\Framework\TestCase;
use Pobo\Sdk\DTO\Blog;
use Pobo\Sdk\DTO\Category;
use Pobo\Sdk\DTO\DeleteResult;
use Pobo\Sdk\DTO\LocalizedString;
use Pobo\Sdk\DTO\Parameter;
use Pobo\Sdk\DTO\ParameterValue;
Expand Down Expand Up @@ -246,4 +247,84 @@ public function testBlogDtoIsConvertedToArray(): void
$this->assertSame('news', $array['category']);
$this->assertTrue($array['is_visible']);
}

public function testDeleteProductsThrowsExceptionForEmptyArray(): void
{
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Payload cannot be empty');

$this->client->deleteProducts([]);
}

public function testDeleteProductsThrowsExceptionForTooManyItems(): void
{
$ids = [];
for ($i = 0; $i < 101; $i++) {
$ids[] = sprintf('PROD-%03d', $i);
}

$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Too many items: 101 provided, maximum is 100');

$this->client->deleteProducts($ids);
}

public function testDeleteCategoriesThrowsExceptionForEmptyArray(): void
{
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Payload cannot be empty');

$this->client->deleteCategories([]);
}

public function testDeleteCategoriesThrowsExceptionForTooManyItems(): void
{
$ids = [];
for ($i = 0; $i < 101; $i++) {
$ids[] = sprintf('CAT-%03d', $i);
}

$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Too many items: 101 provided, maximum is 100');

$this->client->deleteCategories($ids);
}

public function testDeleteBlogsThrowsExceptionForEmptyArray(): void
{
$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Payload cannot be empty');

$this->client->deleteBlogs([]);
}

public function testDeleteBlogsThrowsExceptionForTooManyItems(): void
{
$ids = [];
for ($i = 0; $i < 101; $i++) {
$ids[] = sprintf('BLOG-%03d', $i);
}

$this->expectException(ValidationException::class);
$this->expectExceptionMessage('Too many items: 101 provided, maximum is 100');

$this->client->deleteBlogs($ids);
}

public function testDeleteResultFromArray(): void
{
$result = DeleteResult::fromArray([
'success' => true,
'deleted' => 2,
'skipped' => 1,
'errors' => [
['index' => 2, 'id' => 'PROD-999', 'errors' => ['Product not found']],
],
]);

$this->assertTrue($result->success);
$this->assertSame(2, $result->deleted);
$this->assertSame(1, $result->skipped);
$this->assertTrue($result->hasErrors());
}
}