2025-09-09 20:30:27 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
namespace App\Services\InfoProviderSystem;
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
use App\Entity\Base\AbstractPartsContainingDBElement;
|
2025-09-09 20:30:27 +02:00
|
|
|
use App\Entity\Parts\Part;
|
|
|
|
|
use App\Entity\Parts\Supplier;
|
|
|
|
|
use App\Services\InfoProviderSystem\DTOs\BulkSearchResultDTO;
|
2025-09-19 16:28:40 +02:00
|
|
|
use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO;
|
|
|
|
|
use App\Services\InfoProviderSystem\DTOs\FieldMappingDTO;
|
|
|
|
|
use App\Services\InfoProviderSystem\DTOs\PartSearchResultDTO;
|
|
|
|
|
use App\Services\InfoProviderSystem\DTOs\SearchResultWithMetadataDTO;
|
2025-09-09 20:30:27 +02:00
|
|
|
use App\Services\InfoProviderSystem\Providers\BatchInfoProviderInterface;
|
|
|
|
|
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
|
|
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
|
|
|
use Psr\Log\LoggerInterface;
|
|
|
|
|
use Symfony\Component\HttpClient\Exception\ClientException;
|
|
|
|
|
|
|
|
|
|
final class BulkInfoProviderService
|
|
|
|
|
{
|
2025-09-19 16:28:40 +02:00
|
|
|
/** @var array<string, Supplier|null> Cache for normalized supplier names */
|
|
|
|
|
private array $supplierCache = [];
|
|
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
public function __construct(
|
|
|
|
|
private readonly PartInfoRetriever $infoRetriever,
|
|
|
|
|
private readonly ExistingPartFinder $existingPartFinder,
|
|
|
|
|
private readonly ProviderRegistry $providerRegistry,
|
|
|
|
|
private readonly EntityManagerInterface $entityManager,
|
|
|
|
|
private readonly LoggerInterface $logger
|
|
|
|
|
) {}
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
/**
|
|
|
|
|
* Perform bulk search across multiple parts and providers.
|
|
|
|
|
*
|
|
|
|
|
* @param Part[] $parts Array of parts to search for
|
|
|
|
|
* @param FieldMappingDTO[] $fieldMappings Array of field mappings defining search strategy
|
|
|
|
|
* @param bool $prefetchDetails Whether to prefetch detailed information for results
|
|
|
|
|
* @return BulkSearchResponseDTO Structured response containing all search results
|
|
|
|
|
* @throws \InvalidArgumentException If no valid parts provided
|
|
|
|
|
* @throws \RuntimeException If no search results found for any parts
|
|
|
|
|
*/
|
|
|
|
|
public function performBulkSearch(array $parts, array $fieldMappings, bool $prefetchDetails = false): BulkSearchResponseDTO
|
2025-09-09 20:30:27 +02:00
|
|
|
{
|
|
|
|
|
if (empty($parts)) {
|
|
|
|
|
throw new \InvalidArgumentException('No valid parts found for bulk import');
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
$partResults = [];
|
2025-09-09 20:30:27 +02:00
|
|
|
$hasAnyResults = false;
|
|
|
|
|
|
|
|
|
|
// Group providers by batch capability
|
|
|
|
|
$batchProviders = [];
|
|
|
|
|
$regularProviders = [];
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
foreach ($fieldMappings as $mapping) {
|
|
|
|
|
foreach ($mapping->providers as $providerKey) {
|
2025-09-09 20:30:27 +02:00
|
|
|
if (!is_string($providerKey)) {
|
|
|
|
|
$this->logger->error('Invalid provider key type', [
|
|
|
|
|
'providerKey' => $providerKey,
|
|
|
|
|
'type' => gettype($providerKey)
|
|
|
|
|
]);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
$provider = $this->providerRegistry->getProviderByKey($providerKey);
|
|
|
|
|
if ($provider instanceof BatchInfoProviderInterface) {
|
|
|
|
|
$batchProviders[$providerKey] = $provider;
|
|
|
|
|
} else {
|
|
|
|
|
$regularProviders[$providerKey] = $provider;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Process batch providers first (more efficient)
|
2025-09-19 16:28:40 +02:00
|
|
|
$batchResults = $this->processBatchProviders($parts, $fieldMappings, $batchProviders);
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
// Process regular providers
|
2025-09-19 16:28:40 +02:00
|
|
|
$regularResults = $this->processRegularProviders($parts, $fieldMappings, $regularProviders, $batchResults);
|
2025-09-09 20:30:27 +02:00
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
// Combine and format results for each part
|
2025-09-09 20:30:27 +02:00
|
|
|
foreach ($parts as $part) {
|
2025-09-19 16:28:40 +02:00
|
|
|
$searchResults = [];
|
2025-09-09 20:30:27 +02:00
|
|
|
|
|
|
|
|
// Get results from batch and regular processing
|
|
|
|
|
$allResults = array_merge(
|
|
|
|
|
$batchResults[$part->getId()] ?? [],
|
|
|
|
|
$regularResults[$part->getId()] ?? []
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!empty($allResults)) {
|
|
|
|
|
$hasAnyResults = true;
|
2025-09-19 16:28:40 +02:00
|
|
|
$searchResults = $this->formatSearchResults($allResults);
|
2025-09-09 20:30:27 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
$partResults[] = new PartSearchResultDTO(
|
|
|
|
|
part: $part,
|
|
|
|
|
searchResults: $searchResults,
|
|
|
|
|
errors: []
|
|
|
|
|
);
|
2025-09-09 20:30:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$hasAnyResults) {
|
|
|
|
|
throw new \RuntimeException('No search results found for any of the selected parts');
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
$response = new BulkSearchResponseDTO($partResults);
|
|
|
|
|
|
|
|
|
|
// Prefetch details if requested
|
|
|
|
|
if ($prefetchDetails) {
|
|
|
|
|
$this->prefetchDetailsForResults($response);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $response;
|
2025-09-09 20:30:27 +02:00
|
|
|
}
|
|
|
|
|
|
2025-09-14 23:14:00 +02:00
|
|
|
/**
|
2025-09-19 16:28:40 +02:00
|
|
|
* Process parts using batch-capable info providers.
|
|
|
|
|
*
|
|
|
|
|
* @param Part[] $parts Array of parts to search for
|
|
|
|
|
* @param FieldMappingDTO[] $fieldMappings Array of field mapping configurations
|
|
|
|
|
* @param array<string, BatchInfoProviderInterface> $batchProviders Batch providers indexed by key
|
|
|
|
|
* @return array<int, BulkSearchResultDTO[]> Results indexed by part ID
|
2025-09-14 23:14:00 +02:00
|
|
|
*/
|
2025-09-09 20:30:27 +02:00
|
|
|
private function processBatchProviders(array $parts, array $fieldMappings, array $batchProviders): array
|
|
|
|
|
{
|
|
|
|
|
$batchResults = [];
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
foreach ($batchProviders as $providerKey => $provider) {
|
|
|
|
|
$keywords = $this->collectKeywordsForProvider($parts, $fieldMappings, $providerKey);
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
if (empty($keywords)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$providerResults = $provider->searchByKeywordsBatch($keywords);
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
// Map results back to parts
|
|
|
|
|
foreach ($parts as $part) {
|
|
|
|
|
foreach ($fieldMappings as $mapping) {
|
2025-09-19 16:28:40 +02:00
|
|
|
if (!in_array($providerKey, $mapping->providers, true)) {
|
2025-09-09 20:30:27 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
$keyword = $this->getKeywordFromField($part, $mapping->field);
|
2025-09-09 20:30:27 +02:00
|
|
|
if ($keyword && isset($providerResults[$keyword])) {
|
|
|
|
|
foreach ($providerResults[$keyword] as $dto) {
|
|
|
|
|
$batchResults[$part->getId()][] = new BulkSearchResultDTO(
|
|
|
|
|
baseDto: $dto,
|
2025-09-19 16:28:40 +02:00
|
|
|
sourceField: $mapping->field,
|
2025-09-09 20:30:27 +02:00
|
|
|
sourceKeyword: $keyword,
|
|
|
|
|
localPart: $this->existingPartFinder->findFirstExisting($dto),
|
2025-09-19 16:28:40 +02:00
|
|
|
priority: $mapping->priority
|
2025-09-09 20:30:27 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->logger->error('Batch search failed for provider ' . $providerKey, [
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
'provider' => $providerKey
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $batchResults;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-14 22:56:12 +02:00
|
|
|
/**
|
2025-09-19 16:28:40 +02:00
|
|
|
* Process parts using regular (non-batch) info providers.
|
|
|
|
|
*
|
|
|
|
|
* @param Part[] $parts Array of parts to search for
|
|
|
|
|
* @param FieldMappingDTO[] $fieldMappings Array of field mapping configurations
|
|
|
|
|
* @param array<string, InfoProviderInterface> $regularProviders Regular providers indexed by key
|
|
|
|
|
* @param array<int, BulkSearchResultDTO[]> $excludeResults Results to exclude (from batch processing)
|
|
|
|
|
* @return array<int, BulkSearchResultDTO[]> Results indexed by part ID
|
2025-09-14 22:56:12 +02:00
|
|
|
*/
|
2025-09-09 20:30:27 +02:00
|
|
|
private function processRegularProviders(array $parts, array $fieldMappings, array $regularProviders, array $excludeResults): array
|
|
|
|
|
{
|
|
|
|
|
$regularResults = [];
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
foreach ($parts as $part) {
|
|
|
|
|
$regularResults[$part->getId()] = [];
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
// Skip if we already have batch results for this part
|
|
|
|
|
if (!empty($excludeResults[$part->getId()] ?? [])) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($fieldMappings as $mapping) {
|
2025-09-19 16:28:40 +02:00
|
|
|
$providers = array_intersect($mapping->providers, array_keys($regularProviders));
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
if (empty($providers)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
$keyword = $this->getKeywordFromField($part, $mapping->field);
|
2025-09-09 20:30:27 +02:00
|
|
|
if (!$keyword) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$dtos = $this->infoRetriever->searchByKeyword($keyword, $providers);
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
foreach ($dtos as $dto) {
|
|
|
|
|
$regularResults[$part->getId()][] = new BulkSearchResultDTO(
|
|
|
|
|
baseDto: $dto,
|
2025-09-19 16:28:40 +02:00
|
|
|
sourceField: $mapping->field,
|
2025-09-09 20:30:27 +02:00
|
|
|
sourceKeyword: $keyword,
|
|
|
|
|
localPart: $this->existingPartFinder->findFirstExisting($dto),
|
2025-09-19 16:28:40 +02:00
|
|
|
priority: $mapping->priority
|
2025-09-09 20:30:27 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
} catch (ClientException $e) {
|
|
|
|
|
$this->logger->error('Regular search failed', [
|
|
|
|
|
'part_id' => $part->getId(),
|
2025-09-19 16:28:40 +02:00
|
|
|
'field' => $mapping->field,
|
2025-09-09 20:30:27 +02:00
|
|
|
'error' => $e->getMessage()
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $regularResults;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-14 23:14:00 +02:00
|
|
|
/**
|
2025-09-19 16:28:40 +02:00
|
|
|
* Collect unique keywords for a specific provider from all parts and field mappings.
|
|
|
|
|
*
|
|
|
|
|
* @param Part[] $parts Array of parts to collect keywords from
|
|
|
|
|
* @param FieldMappingDTO[] $fieldMappings Array of field mapping configurations
|
|
|
|
|
* @param string $providerKey The provider key to collect keywords for
|
|
|
|
|
* @return string[] Array of unique keywords
|
2025-09-14 23:14:00 +02:00
|
|
|
*/
|
2025-09-09 20:30:27 +02:00
|
|
|
private function collectKeywordsForProvider(array $parts, array $fieldMappings, string $providerKey): array
|
|
|
|
|
{
|
|
|
|
|
$keywords = [];
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
foreach ($parts as $part) {
|
|
|
|
|
foreach ($fieldMappings as $mapping) {
|
2025-09-19 16:28:40 +02:00
|
|
|
if (!in_array($providerKey, $mapping->providers, true)) {
|
2025-09-09 20:30:27 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
$keyword = $this->getKeywordFromField($part, $mapping->field);
|
2025-09-09 20:30:27 +02:00
|
|
|
if ($keyword && !in_array($keyword, $keywords, true)) {
|
|
|
|
|
$keywords[] = $keyword;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $keywords;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getKeywordFromField(Part $part, string $field): ?string
|
|
|
|
|
{
|
|
|
|
|
return match ($field) {
|
|
|
|
|
'mpn' => $part->getManufacturerProductNumber(),
|
|
|
|
|
'name' => $part->getName(),
|
|
|
|
|
default => $this->getSupplierPartNumber($part, $field)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function getSupplierPartNumber(Part $part, string $field): ?string
|
|
|
|
|
{
|
|
|
|
|
if (!str_ends_with($field, '_spn')) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$supplierKey = substr($field, 0, -4);
|
2025-09-19 16:28:40 +02:00
|
|
|
$supplier = $this->getSupplierByNormalizedName($supplierKey);
|
|
|
|
|
|
|
|
|
|
if (!$supplier) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$orderDetail = $part->getOrderdetails()->filter(
|
|
|
|
|
fn($od) => $od->getSupplier()?->getId() === $supplier->getId()
|
|
|
|
|
)->first();
|
|
|
|
|
|
|
|
|
|
return $orderDetail !== false ? $orderDetail->getSupplierpartnr() : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get supplier by normalized name with caching to prevent N+1 queries.
|
|
|
|
|
*
|
|
|
|
|
* @param string $normalizedKey The normalized supplier key to search for
|
|
|
|
|
* @return Supplier|null The matching supplier or null if not found
|
|
|
|
|
*/
|
|
|
|
|
private function getSupplierByNormalizedName(string $normalizedKey): ?Supplier
|
|
|
|
|
{
|
|
|
|
|
// Check cache first
|
|
|
|
|
if (isset($this->supplierCache[$normalizedKey])) {
|
|
|
|
|
return $this->supplierCache[$normalizedKey];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use efficient database query with PHP normalization
|
|
|
|
|
// Since DQL doesn't support REPLACE, we'll load all suppliers once and cache the normalization
|
|
|
|
|
if (empty($this->supplierCache)) {
|
|
|
|
|
$this->loadSuppliersIntoCache();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$supplier = $this->supplierCache[$normalizedKey] ?? null;
|
|
|
|
|
|
|
|
|
|
// Cache the result (including null results to prevent repeated queries)
|
|
|
|
|
$this->supplierCache[$normalizedKey] = $supplier;
|
|
|
|
|
|
|
|
|
|
return $supplier;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Load all suppliers into cache with normalized names to avoid N+1 queries.
|
|
|
|
|
*/
|
|
|
|
|
private function loadSuppliersIntoCache(): void
|
|
|
|
|
{
|
|
|
|
|
/** @var Supplier[] $suppliers */
|
2025-09-09 20:30:27 +02:00
|
|
|
$suppliers = $this->entityManager->getRepository(Supplier::class)->findAll();
|
|
|
|
|
|
|
|
|
|
foreach ($suppliers as $supplier) {
|
|
|
|
|
$normalizedName = strtolower(str_replace([' ', '-', '_'], '_', $supplier->getName()));
|
2025-09-19 16:28:40 +02:00
|
|
|
$this->supplierCache[$normalizedName] = $supplier;
|
2025-09-09 20:30:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
/**
|
|
|
|
|
* Format and deduplicate search results.
|
|
|
|
|
*
|
|
|
|
|
* @param BulkSearchResultDTO[] $bulkResults Array of bulk search results
|
|
|
|
|
* @return SearchResultWithMetadataDTO[] Array of formatted search results with metadata
|
|
|
|
|
*/
|
2025-09-09 20:30:27 +02:00
|
|
|
private function formatSearchResults(array $bulkResults): array
|
|
|
|
|
{
|
|
|
|
|
// Sort by priority and remove duplicates
|
|
|
|
|
usort($bulkResults, fn($a, $b) => $a->priority <=> $b->priority);
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
$uniqueResults = [];
|
|
|
|
|
$seenKeys = [];
|
2025-09-14 22:56:12 +02:00
|
|
|
|
2025-09-09 20:30:27 +02:00
|
|
|
foreach ($bulkResults as $result) {
|
2025-09-19 16:28:40 +02:00
|
|
|
$key = "{$result->getProviderKey()}|{$result->getProviderId()}";
|
2025-09-09 20:30:27 +02:00
|
|
|
if (!in_array($key, $seenKeys, true)) {
|
|
|
|
|
$seenKeys[] = $key;
|
2025-09-19 16:28:40 +02:00
|
|
|
$uniqueResults[] = new SearchResultWithMetadataDTO(
|
|
|
|
|
searchResult: $result,
|
|
|
|
|
localPart: $result->localPart,
|
|
|
|
|
sourceField: $result->sourceField,
|
|
|
|
|
sourceKeyword: $result->sourceKeyword
|
|
|
|
|
);
|
2025-09-09 20:30:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $uniqueResults;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
/**
|
|
|
|
|
* Prefetch detailed information for search results.
|
|
|
|
|
*
|
|
|
|
|
* @param BulkSearchResponseDTO|array $searchResults Search results (supports both new DTO and legacy array format)
|
|
|
|
|
*/
|
|
|
|
|
public function prefetchDetailsForResults($searchResults): void
|
2025-09-09 20:30:27 +02:00
|
|
|
{
|
|
|
|
|
$prefetchCount = 0;
|
|
|
|
|
|
2025-09-19 16:28:40 +02:00
|
|
|
// Handle both new DTO format and legacy array format for backwards compatibility
|
|
|
|
|
if ($searchResults instanceof BulkSearchResponseDTO) {
|
|
|
|
|
foreach ($searchResults->partResults as $partResult) {
|
|
|
|
|
foreach ($partResult->searchResults as $result) {
|
|
|
|
|
$dto = $result->searchResult;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$this->infoRetriever->getDetails($dto->getProviderKey(), $dto->getProviderId());
|
|
|
|
|
$prefetchCount++;
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->logger->warning('Failed to prefetch details for provider part', [
|
|
|
|
|
'provider_key' => $dto->getProviderKey(),
|
|
|
|
|
'provider_id' => $dto->getProviderId(),
|
|
|
|
|
'error' => $e->getMessage()
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Legacy array format support
|
|
|
|
|
foreach ($searchResults as $partResult) {
|
|
|
|
|
foreach ($partResult['search_results'] as $result) {
|
|
|
|
|
$dto = $result['dto'];
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
$this->infoRetriever->getDetails($dto->getProviderKey(), $dto->getProviderId());
|
|
|
|
|
$prefetchCount++;
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->logger->warning('Failed to prefetch details for provider part', [
|
|
|
|
|
'provider_key' => $dto->getProviderKey(),
|
|
|
|
|
'provider_id' => $dto->getProviderId(),
|
|
|
|
|
'error' => $e->getMessage()
|
|
|
|
|
]);
|
|
|
|
|
}
|
2025-09-09 20:30:27 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->logger->info("Prefetched details for {$prefetchCount} search results");
|
|
|
|
|
}
|
2025-09-14 22:56:12 +02:00
|
|
|
}
|