Renamed dto to make their relation to batch searches more clear

This commit is contained in:
Jan Böhmer 2025-09-21 17:49:00 +02:00
parent 16126c4000
commit 92cd645945
9 changed files with 53 additions and 54 deletions

View file

@ -30,8 +30,8 @@ use App\Entity\Parts\Supplier;
use App\Form\InfoProviderSystem\GlobalFieldMappingType; use App\Form\InfoProviderSystem\GlobalFieldMappingType;
use App\Services\InfoProviderSystem\BulkInfoProviderService; use App\Services\InfoProviderSystem\BulkInfoProviderService;
use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO;
use App\Services\InfoProviderSystem\DTOs\FieldMappingDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO;
use App\Services\InfoProviderSystem\DTOs\PartSearchResultsDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultsDTO;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@ -60,13 +60,13 @@ class BulkInfoProviderImportController extends AbstractController
* Convert field mappings from array format to FieldMappingDTO[]. * Convert field mappings from array format to FieldMappingDTO[].
* *
* @param array $fieldMappings Array of field mapping arrays * @param array $fieldMappings Array of field mapping arrays
* @return FieldMappingDTO[] Array of FieldMappingDTO objects * @return BulkSearchFieldMappingDTO[] Array of FieldMappingDTO objects
*/ */
private function convertFieldMappingsToDto(array $fieldMappings): array private function convertFieldMappingsToDto(array $fieldMappings): array
{ {
$dtos = []; $dtos = [];
foreach ($fieldMappings as $mapping) { foreach ($fieldMappings as $mapping) {
$dtos[] = new FieldMappingDTO(field: $mapping['field'], providers: $mapping['providers'], priority: $mapping['priority'] ?? 1); $dtos[] = new BulkSearchFieldMappingDTO(field: $mapping['field'], providers: $mapping['providers'], priority: $mapping['priority'] ?? 1);
} }
return $dtos; return $dtos;
} }
@ -101,7 +101,7 @@ class BulkInfoProviderImportController extends AbstractController
return $job; return $job;
} }
private function updatePartSearchResults(BulkInfoProviderImportJob $job, int $partId, ?PartSearchResultsDTO $newResults): void private function updatePartSearchResults(BulkInfoProviderImportJob $job, int $partId, ?BulkSearchPartResultsDTO $newResults): void
{ {
if ($newResults === null) { if ($newResults === null) {
return; return;

View file

@ -26,7 +26,7 @@ use App\Entity\Base\AbstractDBElement;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Entity\UserSystem\User; use App\Entity\UserSystem\User;
use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO;
use App\Services\InfoProviderSystem\DTOs\FieldMappingDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types; use Doctrine\DBAL\Types\Types;
@ -44,7 +44,7 @@ class BulkInfoProviderImportJob extends AbstractDBElement
private array $fieldMappings = []; private array $fieldMappings = [];
/** /**
* @var FieldMappingDTO[] The deserialized field mappings DTOs, cached for performance * @var BulkSearchFieldMappingDTO[] The deserialized field mappings DTOs, cached for performance
*/ */
private ?array $fieldMappingsDTO = null; private ?array $fieldMappingsDTO = null;
@ -156,14 +156,14 @@ class BulkInfoProviderImportJob extends AbstractDBElement
} }
/** /**
* @return FieldMappingDTO[] The deserialized field mappings * @return BulkSearchFieldMappingDTO[] The deserialized field mappings
*/ */
public function getFieldMappings(): array public function getFieldMappings(): array
{ {
if ($this->fieldMappingsDTO === null) { if ($this->fieldMappingsDTO === null) {
// Lazy load the DTOs from the raw JSON data // Lazy load the DTOs from the raw JSON data
$this->fieldMappingsDTO = array_map( $this->fieldMappingsDTO = array_map(
static fn($data) => FieldMappingDTO::fromSerializableArray($data), static fn($data) => BulkSearchFieldMappingDTO::fromSerializableArray($data),
$this->fieldMappings $this->fieldMappings
); );
} }
@ -172,20 +172,20 @@ class BulkInfoProviderImportJob extends AbstractDBElement
} }
/** /**
* @param FieldMappingDTO[] $fieldMappings * @param BulkSearchFieldMappingDTO[] $fieldMappings
* @return $this * @return $this
*/ */
public function setFieldMappings(array $fieldMappings): self public function setFieldMappings(array $fieldMappings): self
{ {
//Ensure that we are dealing with the objects here //Ensure that we are dealing with the objects here
if (count($fieldMappings) > 0 && !$fieldMappings[0] instanceof FieldMappingDTO) { if (count($fieldMappings) > 0 && !$fieldMappings[0] instanceof BulkSearchFieldMappingDTO) {
throw new \InvalidArgumentException('Expected an array of FieldMappingDTO objects'); throw new \InvalidArgumentException('Expected an array of FieldMappingDTO objects');
} }
$this->fieldMappingsDTO = $fieldMappings; $this->fieldMappingsDTO = $fieldMappings;
$this->fieldMappings = array_map( $this->fieldMappings = array_map(
static fn(FieldMappingDTO $dto) => $dto->toSerializableArray(), static fn(BulkSearchFieldMappingDTO $dto) => $dto->toSerializableArray(),
$fieldMappings $fieldMappings
); );
return $this; return $this;

View file

@ -6,10 +6,10 @@ namespace App\Services\InfoProviderSystem;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Entity\Parts\Supplier; use App\Entity\Parts\Supplier;
use App\Services\InfoProviderSystem\DTOs\BulkSearchResultDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultDTO;
use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO;
use App\Services\InfoProviderSystem\DTOs\FieldMappingDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO;
use App\Services\InfoProviderSystem\DTOs\PartSearchResultsDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultsDTO;
use App\Services\InfoProviderSystem\Providers\BatchInfoProviderInterface; use App\Services\InfoProviderSystem\Providers\BatchInfoProviderInterface;
use App\Services\InfoProviderSystem\Providers\InfoProviderInterface; use App\Services\InfoProviderSystem\Providers\InfoProviderInterface;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
@ -33,7 +33,7 @@ final class BulkInfoProviderService
* Perform bulk search across multiple parts and providers. * Perform bulk search across multiple parts and providers.
* *
* @param Part[] $parts Array of parts to search for * @param Part[] $parts Array of parts to search for
* @param FieldMappingDTO[] $fieldMappings Array of field mappings defining search strategy * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mappings defining search strategy
* @param bool $prefetchDetails Whether to prefetch detailed information for results * @param bool $prefetchDetails Whether to prefetch detailed information for results
* @return BulkSearchResponseDTO Structured response containing all search results * @return BulkSearchResponseDTO Structured response containing all search results
* @throws \InvalidArgumentException If no valid parts provided * @throws \InvalidArgumentException If no valid parts provided
@ -92,7 +92,7 @@ final class BulkInfoProviderService
$searchResults = $this->formatSearchResults($allResults); $searchResults = $this->formatSearchResults($allResults);
} }
$partResults[] = new PartSearchResultsDTO( $partResults[] = new BulkSearchPartResultsDTO(
part: $part, part: $part,
searchResults: $searchResults, searchResults: $searchResults,
errors: [] errors: []
@ -117,9 +117,9 @@ final class BulkInfoProviderService
* Process parts using batch-capable info providers. * Process parts using batch-capable info providers.
* *
* @param Part[] $parts Array of parts to search for * @param Part[] $parts Array of parts to search for
* @param FieldMappingDTO[] $fieldMappings Array of field mapping configurations * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations
* @param array<string, BatchInfoProviderInterface> $batchProviders Batch providers indexed by key * @param array<string, BatchInfoProviderInterface> $batchProviders Batch providers indexed by key
* @return array<int, BulkSearchResultDTO[]> Results indexed by part ID * @return array<int, BulkSearchPartResultDTO[]> Results indexed by part ID
*/ */
private function processBatchProviders(array $parts, array $fieldMappings, array $batchProviders): array private function processBatchProviders(array $parts, array $fieldMappings, array $batchProviders): array
{ {
@ -145,7 +145,7 @@ final class BulkInfoProviderService
$keyword = $this->getKeywordFromField($part, $mapping->field); $keyword = $this->getKeywordFromField($part, $mapping->field);
if ($keyword && isset($providerResults[$keyword])) { if ($keyword && isset($providerResults[$keyword])) {
foreach ($providerResults[$keyword] as $dto) { foreach ($providerResults[$keyword] as $dto) {
$batchResults[$part->getId()][] = new BulkSearchResultDTO( $batchResults[$part->getId()][] = new BulkSearchPartResultDTO(
searchResult: $dto, searchResult: $dto,
sourceField: $mapping->field, sourceField: $mapping->field,
sourceKeyword: $keyword, sourceKeyword: $keyword,
@ -171,10 +171,10 @@ final class BulkInfoProviderService
* Process parts using regular (non-batch) info providers. * Process parts using regular (non-batch) info providers.
* *
* @param Part[] $parts Array of parts to search for * @param Part[] $parts Array of parts to search for
* @param FieldMappingDTO[] $fieldMappings Array of field mapping configurations * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations
* @param array<string, InfoProviderInterface> $regularProviders Regular providers indexed by key * @param array<string, InfoProviderInterface> $regularProviders Regular providers indexed by key
* @param array<int, BulkSearchResultDTO[]> $excludeResults Results to exclude (from batch processing) * @param array<int, BulkSearchPartResultDTO[]> $excludeResults Results to exclude (from batch processing)
* @return array<int, BulkSearchResultDTO[]> Results indexed by part ID * @return array<int, BulkSearchPartResultDTO[]> Results indexed by part ID
*/ */
private function processRegularProviders(array $parts, array $fieldMappings, array $regularProviders, array $excludeResults): array private function processRegularProviders(array $parts, array $fieldMappings, array $regularProviders, array $excludeResults): array
{ {
@ -204,7 +204,7 @@ final class BulkInfoProviderService
$dtos = $this->infoRetriever->searchByKeyword($keyword, $providers); $dtos = $this->infoRetriever->searchByKeyword($keyword, $providers);
foreach ($dtos as $dto) { foreach ($dtos as $dto) {
$regularResults[$part->getId()][] = new BulkSearchResultDTO( $regularResults[$part->getId()][] = new BulkSearchPartResultDTO(
searchResult: $dto, searchResult: $dto,
sourceField: $mapping->field, sourceField: $mapping->field,
sourceKeyword: $keyword, sourceKeyword: $keyword,
@ -229,7 +229,7 @@ final class BulkInfoProviderService
* Collect unique keywords for a specific provider from all parts and field mappings. * Collect unique keywords for a specific provider from all parts and field mappings.
* *
* @param Part[] $parts Array of parts to collect keywords from * @param Part[] $parts Array of parts to collect keywords from
* @param FieldMappingDTO[] $fieldMappings Array of field mapping configurations * @param BulkSearchFieldMappingDTO[] $fieldMappings Array of field mapping configurations
* @param string $providerKey The provider key to collect keywords for * @param string $providerKey The provider key to collect keywords for
* @return string[] Array of unique keywords * @return string[] Array of unique keywords
*/ */
@ -326,8 +326,8 @@ final class BulkInfoProviderService
/** /**
* Format and deduplicate search results. * Format and deduplicate search results.
* *
* @param BulkSearchResultDTO[] $bulkResults Array of bulk search results * @param BulkSearchPartResultDTO[] $bulkResults Array of bulk search results
* @return BulkSearchResultDTO[] Array of formatted search results with metadata * @return BulkSearchPartResultDTO[] Array of formatted search results with metadata
*/ */
private function formatSearchResults(array $bulkResults): array private function formatSearchResults(array $bulkResults): array
{ {

View file

@ -24,9 +24,8 @@ namespace App\Services\InfoProviderSystem\DTOs;
/** /**
* Represents a mapping between a part field and the info providers that should search in that field. * Represents a mapping between a part field and the info providers that should search in that field.
* This DTO provides type safety and better structure than raw arrays for field mapping configuration.
*/ */
readonly class FieldMappingDTO readonly class BulkSearchFieldMappingDTO
{ {
/** /**
* @param string $field The field to search in (e.g., 'mpn', 'name', or supplier-specific fields like 'digikey_spn') * @param string $field The field to search in (e.g., 'mpn', 'name', or supplier-specific fields like 'digikey_spn')

View file

@ -25,9 +25,9 @@ namespace App\Services\InfoProviderSystem\DTOs;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
/** /**
* Represents a search result from bulk search with additional context information, like how the part was found. * Represents a single search result from bulk search with additional context information, like how the part was found.
*/ */
readonly class BulkSearchResultDTO readonly class BulkSearchPartResultDTO
{ {
public function __construct( public function __construct(
/** The base search result DTO containing provider data */ /** The base search result DTO containing provider data */

View file

@ -26,13 +26,13 @@ use App\Entity\Parts\Part;
/** /**
* Represents the search results for a single part from bulk info provider search. * Represents the search results for a single part from bulk info provider search.
* This DTO provides type safety and clear structure for part search results. * It contains multiple search results, that match the part.
*/ */
readonly class PartSearchResultsDTO readonly class BulkSearchPartResultsDTO
{ {
/** /**
* @param Part $part The part that was searched for * @param Part $part The part that was searched for
* @param BulkSearchResultDTO[] $searchResults Array of search results found for this part * @param BulkSearchPartResultDTO[] $searchResults Array of search results found for this part
* @param string[] $errors Array of error messages encountered during search * @param string[] $errors Array of error messages encountered during search
*/ */
public function __construct( public function __construct(
@ -72,12 +72,12 @@ readonly class PartSearchResultsDTO
/** /**
* Get search results sorted by priority (ascending). * Get search results sorted by priority (ascending).
* @return BulkSearchResultDTO[] * @return BulkSearchPartResultDTO[]
*/ */
public function getResultsSortedByPriority(): array public function getResultsSortedByPriority(): array
{ {
$results = $this->searchResults; $results = $this->searchResults;
usort($results, static fn(BulkSearchResultDTO $a, BulkSearchResultDTO $b) => $a->priority <=> $b->priority); usort($results, static fn(BulkSearchPartResultDTO $a, BulkSearchPartResultDTO $b) => $a->priority <=> $b->priority);
return $results; return $results;
} }
} }

View file

@ -32,7 +32,7 @@ use Doctrine\ORM\EntityManagerInterface;
readonly class BulkSearchResponseDTO implements \ArrayAccess readonly class BulkSearchResponseDTO implements \ArrayAccess
{ {
/** /**
* @param PartSearchResultsDTO[] $partResults Array of search results for each part * @param BulkSearchPartResultsDTO[] $partResults Array of search results for each part
*/ */
public function __construct( public function __construct(
public array $partResults public array $partResults
@ -41,10 +41,10 @@ readonly class BulkSearchResponseDTO implements \ArrayAccess
/** /**
* Replaces the search results for a specific part, and returns a new instance. * Replaces the search results for a specific part, and returns a new instance.
* @param Part|int $part * @param Part|int $part
* @param PartSearchResultsDTO $new_results * @param BulkSearchPartResultsDTO $new_results
* @return BulkSearchResponseDTO * @return BulkSearchResponseDTO
*/ */
public function replaceResultsForPart(Part|int $part, PartSearchResultsDTO $new_results): self public function replaceResultsForPart(Part|int $part, BulkSearchPartResultsDTO $new_results): self
{ {
$array = $this->partResults; $array = $this->partResults;
foreach ($array as $index => $partResult) { foreach ($array as $index => $partResult) {
@ -85,7 +85,7 @@ readonly class BulkSearchResponseDTO implements \ArrayAccess
/** /**
* Get all parts that have search results. * Get all parts that have search results.
* @return PartSearchResultsDTO[] * @return BulkSearchPartResultsDTO[]
*/ */
public function getPartsWithResults(): array public function getPartsWithResults(): array
{ {
@ -94,7 +94,7 @@ readonly class BulkSearchResponseDTO implements \ArrayAccess
/** /**
* Get all parts that have errors. * Get all parts that have errors.
* @return PartSearchResultsDTO[] * @return BulkSearchPartResultsDTO[]
*/ */
public function getPartsWithErrors(): array public function getPartsWithErrors(): array
{ {
@ -175,9 +175,9 @@ readonly class BulkSearchResponseDTO implements \ArrayAccess
{ {
$partResults = []; $partResults = [];
foreach ($data as $partData) { foreach ($data as $partData) {
$partResults[] = new PartSearchResultsDTO( $partResults[] = new BulkSearchPartResultsDTO(
part: $entityManager->getReference(Part::class, $partData['part_id']), part: $entityManager->getReference(Part::class, $partData['part_id']),
searchResults: array_map(fn($result) => new BulkSearchResultDTO( searchResults: array_map(fn($result) => new BulkSearchPartResultDTO(
searchResult: SearchResultDTO::fromNormalizedSearchResultArray($result['dto']), searchResult: SearchResultDTO::fromNormalizedSearchResultArray($result['dto']),
sourceField: $result['source_field'] ?? null, sourceField: $result['source_field'] ?? null,
sourceKeyword: $result['source_keyword'] ?? null, sourceKeyword: $result['source_keyword'] ?? null,
@ -199,7 +199,7 @@ readonly class BulkSearchResponseDTO implements \ArrayAccess
return isset($this->partResults[$offset]); return isset($this->partResults[$offset]);
} }
public function offsetGet(mixed $offset): ?PartSearchResultsDTO public function offsetGet(mixed $offset): ?BulkSearchPartResultsDTO
{ {
if (!is_int($offset)) { if (!is_int($offset)) {
throw new \InvalidArgumentException("Offset must be an integer."); throw new \InvalidArgumentException("Offset must be an integer.");

View file

@ -659,8 +659,8 @@ class BulkInfoProviderImportControllerTest extends WebTestCase
// Create field mappings to verify the service works // Create field mappings to verify the service works
$fieldMappings = [ $fieldMappings = [
new \App\Services\InfoProviderSystem\DTOs\FieldMappingDTO('name', ['test'], 1), new \App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO('name', ['test'], 1),
new \App\Services\InfoProviderSystem\DTOs\FieldMappingDTO('mpn', ['test'], 2) new \App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO('mpn', ['test'], 2)
]; ];
// The service may return an empty result or throw when no results are found // The service may return an empty result or throw when no results are found
@ -790,8 +790,8 @@ class BulkInfoProviderImportControllerTest extends WebTestCase
// Create field mappings with supplier SPN field mapping // Create field mappings with supplier SPN field mapping
$fieldMappings = [ $fieldMappings = [
new \App\Services\InfoProviderSystem\DTOs\FieldMappingDTO('invalid_field', ['test'], 1), new \App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO('invalid_field', ['test'], 1),
new \App\Services\InfoProviderSystem\DTOs\FieldMappingDTO('test_supplier_spn', ['test'], 2) new \App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO('test_supplier_spn', ['test'], 2)
]; ];
// The service should be able to process the request and throw an exception when no results are found // The service should be able to process the request and throw an exception when no results are found
@ -821,7 +821,7 @@ class BulkInfoProviderImportControllerTest extends WebTestCase
// Create field mappings with multiple keywords // Create field mappings with multiple keywords
$fieldMappings = [ $fieldMappings = [
new \App\Services\InfoProviderSystem\DTOs\FieldMappingDTO('name', ['lcsc'], 1) new \App\Services\InfoProviderSystem\DTOs\BulkSearchFieldMappingDTO('name', ['lcsc'], 1)
]; ];
// The service should be able to process the request and throw an exception when no results are found // The service should be able to process the request and throw an exception when no results are found

View file

@ -23,8 +23,8 @@ namespace App\Tests\Services\InfoProviderSystem\DTOs;
use App\Doctrine\Types\BulkSearchResponseDTOType; use App\Doctrine\Types\BulkSearchResponseDTOType;
use App\Entity\Parts\Part; use App\Entity\Parts\Part;
use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchResponseDTO;
use App\Services\InfoProviderSystem\DTOs\BulkSearchResultDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultDTO;
use App\Services\InfoProviderSystem\DTOs\PartSearchResultsDTO; use App\Services\InfoProviderSystem\DTOs\BulkSearchPartResultsDTO;
use App\Services\InfoProviderSystem\DTOs\SearchResultDTO; use App\Services\InfoProviderSystem\DTOs\SearchResultDTO;
use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\EntityManagerInterface;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -45,14 +45,14 @@ class BulkSearchResponseDTOTest extends KernelTestCase
$this->dummyEmpty = new BulkSearchResponseDTO(partResults: []); $this->dummyEmpty = new BulkSearchResponseDTO(partResults: []);
$this->dummy = new BulkSearchResponseDTO(partResults: [ $this->dummy = new BulkSearchResponseDTO(partResults: [
new PartSearchResultsDTO( new BulkSearchPartResultsDTO(
part: $this->entityManager->find(Part::class, 1), part: $this->entityManager->find(Part::class, 1),
searchResults: [ searchResults: [
new BulkSearchResultDTO( new BulkSearchPartResultDTO(
searchResult: new SearchResultDTO(provider_key: "dummy", provider_id: "1234", name: "Test Part", description: "A part for testing"), searchResult: new SearchResultDTO(provider_key: "dummy", provider_id: "1234", name: "Test Part", description: "A part for testing"),
sourceField: "mpn", sourceKeyword: "1234", priority: 1 sourceField: "mpn", sourceKeyword: "1234", priority: 1
), ),
new BulkSearchResultDTO( new BulkSearchPartResultDTO(
searchResult: new SearchResultDTO(provider_key: "test", provider_id: "test", name: "Test Part2", description: "A part for testing"), searchResult: new SearchResultDTO(provider_key: "test", provider_id: "test", name: "Test Part2", description: "A part for testing"),
sourceField: "name", sourceKeyword: "1234", sourceField: "name", sourceKeyword: "1234",
localPart: $this->entityManager->find(Part::class, 2), priority: 2, localPart: $this->entityManager->find(Part::class, 2), priority: 2,