fix stream error handling
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
47
src/Shopware/StoreApiException.php
Normal file
47
src/Shopware/StoreApiException.php
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user