fix stream error handling

This commit is contained in:
team 1
2026-04-25 12:19:20 +02:00
parent 2f28ad0416
commit fa65417efe
9 changed files with 435 additions and 62 deletions

View File

@@ -14,24 +14,61 @@ final class ShopwareCriteriaBuilder
?bool $grouping = true
): array
{
return $this->buildCriteria(
query: $query,
limit: $limit,
grouping: $grouping,
includeRichTextFields: true
);
}
/**
* Builds a smaller Store API criteria payload for retrying Shopware responses
* that fail while JSON-encoding product descriptions or custom fields.
*/
public function buildSafe(
CommerceSearchQuery $query,
?int $limit = 25,
?bool $grouping = true
): array
{
return $this->buildCriteria(
query: $query,
limit: $limit,
grouping: $grouping,
includeRichTextFields: false
);
}
private function buildCriteria(
CommerceSearchQuery $query,
?int $limit,
?bool $grouping,
bool $includeRichTextFields
): array {
$productIncludes = [
'id',
'name',
'productNumber',
'available',
'calculatedPrice',
'seoUrls',
'manufacturer',
'translated.name',
'cover',
];
if ($includeRichTextFields) {
$productIncludes[] = 'description';
$productIncludes[] = 'customFields';
}
$criteria = [
'page' => 1,
'limit' => max(1, $limit),
'total-count-mode' => 0,
'includes' => [
'product' => [
'id',
'name',
'description',
'productNumber',
'available',
'calculatedPrice',
'seoUrls',
'manufacturer',
'translated.name',
'cover',
'customFields'
],
'product' => $productIncludes,
'product_manufacturer' => [
'name',
],
@@ -64,7 +101,7 @@ final class ShopwareCriteriaBuilder
'associations' => [
'media' => [
'associations' => [
"thumbnails" => new \stdClass()
'thumbnails' => new \stdClass()
]
]
]
@@ -73,7 +110,7 @@ final class ShopwareCriteriaBuilder
];
if ($grouping) {
$criteria["grouping"] = ["parentId"];
$criteria['grouping'] = ['parentId'];
}
if ($query->searchText !== '') {
@@ -105,4 +142,4 @@ final class ShopwareCriteriaBuilder
return $criteria;
}
}
}

View File

@@ -26,6 +26,7 @@ final readonly class StoreApiClient
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws ClientExceptionInterface
* @throws StoreApiException
*/
public function searchProducts(array $criteria): array
{
@@ -43,7 +44,7 @@ final readonly class StoreApiClient
$response = $this->httpClient->request('POST', $url, [
'headers' => [
'Content-Type' => 'application/json',
'Content-Type' => 'application/json; charset=utf-8',
'Accept' => 'application/json',
'sw-access-key' => $this->salesChannelAccessKey,
],
@@ -56,22 +57,54 @@ final readonly class StoreApiClient
$content = $this->sanitizeString($content);
if ($statusCode < 200 || $statusCode >= 300) {
throw new RuntimeException(sprintf(
'Shopware Store API request failed with status %d. Response: %s',
$statusCode,
mb_substr(trim($content), 0, 1000)
));
throw $this->buildHttpFailure($statusCode, $content);
}
$data = json_decode($content, true);
if (!is_array($data)) {
throw new RuntimeException('Shopware Store API returned invalid JSON.');
throw new StoreApiException(
'Shopware Store API returned invalid JSON.',
$statusCode,
true,
$this->containsUtf8FailureSignal($content),
true
);
}
return $data;
}
private function buildHttpFailure(int $statusCode, string $content): StoreApiException
{
$preview = mb_substr(trim($content), 0, 1000);
$utf8Failure = $this->containsUtf8FailureSignal($preview);
$serverFailure = $statusCode >= 500;
return new StoreApiException(
sprintf(
'Shopware Store API request failed with status %d. Response: %s',
$statusCode,
$preview
),
$statusCode,
$serverFailure,
$utf8Failure,
$serverFailure || $utf8Failure
);
}
private function containsUtf8FailureSignal(string $content): bool
{
$normalized = mb_strtolower($content, 'UTF-8');
return str_contains($normalized, 'malformed utf-8')
|| str_contains($normalized, 'malformed utf8')
|| str_contains($normalized, 'invalid utf-8')
|| str_contains($normalized, 'invalid utf8')
|| str_contains($normalized, 'possibly incorrectly encoded');
}
private function sanitizeValue(mixed $value): mixed
{
if (is_array($value)) {
@@ -115,4 +148,4 @@ final readonly class StoreApiClient
return '';
}
}
}

View File

@@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace App\Shopware;
use RuntimeException;
use Throwable;
final class StoreApiException extends RuntimeException
{
public function __construct(
string $message,
private readonly ?int $statusCode = null,
private readonly bool $serverFailure = false,
private readonly bool $utf8Failure = false,
private readonly bool $safeCriteriaRetryRecommended = false,
?Throwable $previous = null
) {
parent::__construct($message, 0, $previous);
}
public function getStatusCode(): ?int
{
return $this->statusCode;
}
public function isServerFailure(): bool
{
return $this->serverFailure;
}
public function isUtf8Failure(): bool
{
return $this->utf8Failure;
}
public function isSafeCriteriaRetryRecommended(): bool
{
return $this->safeCriteriaRetryRecommended;
}
public function isSystemFailure(): bool
{
return $this->serverFailure || $this->utf8Failure;
}
}