style: apply CS Fixer formatting across codebase
Some checks are pending
CI / test (push) Waiting to run

Consistent brace style, spacing, and method expansion throughout
domain, infrastructure, and test files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Simon Kuehn 2026-05-19 10:56:37 +00:00
parent fc18958e0e
commit a79791a972
37 changed files with 223 additions and 167 deletions

View file

@ -57,9 +57,6 @@ final class PhotoService
}
}
/**
* @param list<string> $orderedPhotoIds UUIDs in the desired display order
*/
/**
* Attach a file that was already stored (e.g. uploaded at ingest time) to an article.
*

View file

@ -200,6 +200,7 @@ class Article
foreach ($this->attributeValues as $existing) {
if ($existing->getAttributeDefinition()->getId()->equals($value->getAttributeDefinition()->getId())) {
$existing->setValue($value->getValue());
return;
}
}

View file

@ -88,6 +88,7 @@ class ArticleType
$existing->setArticleFieldKey($mapping->getArticleFieldKey());
$existing->setAttributeDefinition($mapping->getAttributeDefinition());
$existing->setRequired($mapping->isRequired());
return;
}
}
@ -105,7 +106,7 @@ class ArticleType
public function getAttributeDefinitions(): Collection
{
return $this->attributeAssignments->map(
fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition()
static fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition()
);
}
@ -113,8 +114,8 @@ class ArticleType
public function getRequiredAttributeDefinitions(): Collection
{
return $this->attributeAssignments
->filter(fn (ArticleTypeAttribute $a) => $a->isRequired())
->map(fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
->filter(static fn (ArticleTypeAttribute $a) => $a->isRequired())
->map(static fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
}
// -------------------------------------------------------------------------
@ -125,15 +126,15 @@ class ArticleType
public function getRequiredAttributeDefs(): Collection
{
return $this->attributeAssignments
->filter(fn (ArticleTypeAttribute $a) => $a->isRequired())
->map(fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
->filter(static fn (ArticleTypeAttribute $a) => $a->isRequired())
->map(static fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
}
/** @param iterable<AttributeDefinition> $defs */
public function setRequiredAttributeDefs(iterable $defs): void
{
/** @var list<AttributeDefinition> $list */
$list = $defs instanceof Collection ? $defs->toArray() : \iterator_to_array($defs, false);
$list = $defs instanceof Collection ? $defs->toArray() : iterator_to_array($defs, false);
$this->pendingRequired = $list;
}
@ -141,15 +142,15 @@ class ArticleType
public function getOptionalAttributeDefs(): Collection
{
return $this->attributeAssignments
->filter(fn (ArticleTypeAttribute $a) => !$a->isRequired())
->map(fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
->filter(static fn (ArticleTypeAttribute $a) => !$a->isRequired())
->map(static fn (ArticleTypeAttribute $a) => $a->getAttributeDefinition());
}
/** @param iterable<AttributeDefinition> $defs */
public function setOptionalAttributeDefs(iterable $defs): void
{
/** @var list<AttributeDefinition> $list */
$list = $defs instanceof Collection ? $defs->toArray() : \iterator_to_array($defs, false);
$list = $defs instanceof Collection ? $defs->toArray() : iterator_to_array($defs, false);
$this->pendingOptional = $list;
}
@ -159,7 +160,7 @@ class ArticleType
*/
public function applyAttributeAssignments(): void
{
if ($this->pendingRequired === null && $this->pendingOptional === null) {
if (null === $this->pendingRequired && null === $this->pendingOptional) {
return;
}

View file

@ -18,7 +18,7 @@ use Symfony\Component\Uid\Uuid;
class ArticleTypeEbayMapping
{
public const SOURCE_ARTICLE_FIELD = 'article_field';
public const SOURCE_ATTRIBUTE = 'attribute';
public const SOURCE_ATTRIBUTE = 'attribute';
#[ORM\Id]
#[ORM\Column(type: 'uuid')]
@ -46,31 +46,61 @@ class ArticleTypeEbayMapping
public function __construct(ArticleType $articleType, string $ebayAspectName, string $sourceType)
{
$this->id = Uuid::v7();
$this->articleType = $articleType;
$this->id = Uuid::v7();
$this->articleType = $articleType;
$this->ebayAspectName = $ebayAspectName;
$this->sourceType = $sourceType;
$this->sourceType = $sourceType;
}
public function getId(): Uuid { return $this->id; }
public function getId(): Uuid
{
return $this->id;
}
public function getArticleType(): ArticleType { return $this->articleType; }
public function getArticleType(): ArticleType
{
return $this->articleType;
}
public function getEbayAspectName(): string { return $this->ebayAspectName; }
public function getEbayAspectName(): string
{
return $this->ebayAspectName;
}
public function getSourceType(): string { return $this->sourceType; }
public function getSourceType(): string
{
return $this->sourceType;
}
public function getArticleFieldKey(): ?string { return $this->articleFieldKey; }
public function getArticleFieldKey(): ?string
{
return $this->articleFieldKey;
}
public function setArticleFieldKey(?string $key): void { $this->articleFieldKey = $key; }
public function setArticleFieldKey(?string $key): void
{
$this->articleFieldKey = $key;
}
public function getAttributeDefinition(): ?AttributeDefinition { return $this->attributeDefinition; }
public function getAttributeDefinition(): ?AttributeDefinition
{
return $this->attributeDefinition;
}
public function setAttributeDefinition(?AttributeDefinition $def): void { $this->attributeDefinition = $def; }
public function setAttributeDefinition(?AttributeDefinition $def): void
{
$this->attributeDefinition = $def;
}
public function isRequired(): bool { return $this->required; }
public function isRequired(): bool
{
return $this->required;
}
public function setRequired(bool $required): void { $this->required = $required; }
public function setRequired(bool $required): void
{
$this->required = $required;
}
public function getSourceLabel(): string
{

View file

@ -37,14 +37,48 @@ class Translation
$this->value = $value;
}
public function getId(): Uuid { return $this->id; }
public function getLocale(): string { return $this->locale; }
public function getDomain(): string { return $this->domain; }
public function getKey(): string { return $this->key; }
public function getValue(): string { return $this->value; }
public function getId(): Uuid
{
return $this->id;
}
public function setLocale(string $locale): void { $this->locale = $locale; }
public function setDomain(string $domain): void { $this->domain = $domain; }
public function setKey(string $key): void { $this->key = $key; }
public function setValue(string $value): void { $this->value = $value; }
public function getLocale(): string
{
return $this->locale;
}
public function getDomain(): string
{
return $this->domain;
}
public function getKey(): string
{
return $this->key;
}
public function getValue(): string
{
return $this->value;
}
public function setLocale(string $locale): void
{
$this->locale = $locale;
}
public function setDomain(string $domain): void
{
$this->domain = $domain;
}
public function setKey(string $key): void
{
$this->key = $key;
}
public function setValue(string $value): void
{
$this->value = $value;
}
}

View file

@ -45,9 +45,9 @@ final class EbayTextAgent
$modelNumber = $article->getModelNumber() ?? '';
$deviceLabel = trim("{$manufacturer} {$modelName} {$modelNumber}") ?: $typeName;
$specsSection = $attributeText !== ''
$specsSection = '' !== $attributeText
? "Known attributes:\n{$attributeText}"
: ($specsText !== '' ? "Research notes:\n".mb_substr($specsText, 0, 1500) : '');
: ('' !== $specsText ? "Research notes:\n".mb_substr($specsText, 0, 1500) : '');
$titlePrompt = $this->prompts->render('ebay_title', [
'typeName' => $typeName,

View file

@ -25,7 +25,7 @@ final class SpecsResearchAgent
*/
public function research(string $modelName, string $articleTypeName, string $manufacturer = '', array $attributeFields = []): array
{
$subject = trim(($manufacturer !== '' ? $manufacturer.' ' : '').$modelName);
$subject = trim(('' !== $manufacturer ? $manufacturer.' ' : '').$modelName);
$searchResults = $this->search->search("{$subject} {$articleTypeName} specifications");
@ -36,7 +36,7 @@ final class SpecsResearchAgent
$prompt = $this->prompts->render('specs_research', [
'articleType' => $articleTypeName,
'subject' => $subject,
'searchResults' => $searchResults !== '' ? $searchResults : 'No web results available.',
'searchResults' => '' !== $searchResults ? $searchResults : 'No web results available.',
'fields' => $fieldsList,
]);

View file

@ -129,7 +129,7 @@ final class MistralClient implements OllamaClientInterface
private function guessMimeType(string $path): string
{
return match (strtolower(pathinfo($path, PATHINFO_EXTENSION))) {
return match (strtolower(pathinfo($path, \PATHINFO_EXTENSION))) {
'jpg', 'jpeg' => 'image/jpeg',
'png' => 'image/png',
'gif' => 'image/gif',

View file

@ -127,7 +127,7 @@ final class FrappeErpAdapter implements ErpAdapterInterface
*/
private function addressMatches(array $addr, string $street, string $city, string $zip): bool
{
$n = static fn(string $s): string => mb_strtolower(trim($s));
$n = static fn (string $s): string => mb_strtolower(trim($s));
return $n($addr['address_line1'] ?? '') === $n($street)
&& $n($addr['city'] ?? '') === $n($city)

View file

@ -50,7 +50,7 @@ final class CreateApiKeyCommand extends Command
$rawKey = bin2hex(random_bytes(24));
$prefix = substr($rawKey, 0, 8);
$keyHash = password_hash($rawKey, PASSWORD_BCRYPT);
$keyHash = password_hash($rawKey, \PASSWORD_BCRYPT);
$apiKey = new ApiKey($user, $label, $prefix, $keyHash);
$this->apiKeyRepository->save($apiKey);

View file

@ -10,10 +10,11 @@ use App\Infrastructure\Messenger\Message\JsonCodingMessage;
use App\Infrastructure\Messenger\Message\PhotoUploadMessage;
use App\Infrastructure\Messenger\Message\SpecsResearchMessage;
use App\Infrastructure\Messenger\Message\ValidationMessage;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminRoute;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
@ -27,7 +28,6 @@ use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Orm\EntityRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Translation\TranslatableMessage;

View file

@ -62,7 +62,7 @@ final class ArticleTypeCrudController extends AbstractCrudController
yield TextField::new('name', 'Name');
yield TextField::new('ebayCategoryId', 'eBay Category ID')->setRequired(false)->hideOnIndex();
yield IntegerField::new('attributeAssignments', '# Attributes')
->formatValue(static fn (mixed $v): int => is_countable($v) ? count($v) : 0)
->formatValue(static fn (mixed $v): int => is_countable($v) ? \count($v) : 0)
->hideOnForm()
->setSortable(false);
@ -77,8 +77,7 @@ final class ArticleTypeCrudController extends AbstractCrudController
'attr' => ['data-ea-widget' => 'ea-autocomplete'],
])
->hideOnIndex()
->formatValue(static fn (mixed $v, ArticleType $at): string =>
implode(', ', $at->getRequiredAttributeDefs()->map(fn (AttributeDefinition $d) => $d->getName())->toArray()) ?: '—'
->formatValue(static fn (mixed $v, ArticleType $at): string => implode(', ', $at->getRequiredAttributeDefs()->map(static fn (AttributeDefinition $d) => $d->getName())->toArray()) ?: '—'
);
yield Field::new('optionalAttributeDefs', 'Optional Attributes')
@ -92,8 +91,7 @@ final class ArticleTypeCrudController extends AbstractCrudController
'attr' => ['data-ea-widget' => 'ea-autocomplete'],
])
->hideOnIndex()
->formatValue(static fn (mixed $v, ArticleType $at): string =>
implode(', ', $at->getOptionalAttributeDefs()->map(fn (AttributeDefinition $d) => $d->getName())->toArray()) ?: '—'
->formatValue(static fn (mixed $v, ArticleType $at): string => implode(', ', $at->getOptionalAttributeDefs()->map(static fn (AttributeDefinition $d) => $d->getName())->toArray()) ?: '—'
);
}

View file

@ -45,7 +45,7 @@ final class EbayAspectImportController extends AbstractController
return $this->json([]);
}
return $this->json(array_slice($results, 0, 15));
return $this->json(\array_slice($results, 0, 15));
}
/**
@ -54,23 +54,23 @@ final class EbayAspectImportController extends AbstractController
*/
private const ARTICLE_FIELDS = [
'articleTypeName' => 'Produktart (Artikel-Typ)',
'manufacturer' => 'Hersteller (Marke)',
'modelNumber' => 'Herstellernummer (PN / MPN)',
'modelName' => 'Modellname',
'serialNumber' => 'Seriennummer',
'manufacturer' => 'Hersteller (Marke)',
'modelNumber' => 'Herstellernummer (PN / MPN)',
'modelName' => 'Modellname',
'serialNumber' => 'Seriennummer',
];
/** eBay aspect names (lowercase) that auto-match to article fields. */
private const ARTICLE_FIELD_ALIASES = [
'produktart' => 'articleTypeName',
'marke' => 'manufacturer',
'brand' => 'manufacturer',
'hersteller' => 'manufacturer',
'herstellernummer' => 'modelNumber',
'mpn' => 'modelNumber',
'teilenummer' => 'modelNumber',
'modell' => 'modelName',
'seriennummer' => 'serialNumber',
'produktart' => 'articleTypeName',
'marke' => 'manufacturer',
'brand' => 'manufacturer',
'hersteller' => 'manufacturer',
'herstellernummer' => 'modelNumber',
'mpn' => 'modelNumber',
'teilenummer' => 'modelNumber',
'modell' => 'modelName',
'seriennummer' => 'serialNumber',
];
#[Route('/admin/ebay/aspect-import/{id}', name: 'admin_ebay_aspect_import')]
@ -109,20 +109,20 @@ final class EbayAspectImportController extends AbstractController
$allDefs = $this->em->getRepository(AttributeDefinition::class)->findBy([], ['name' => 'ASC']);
$rows = $this->buildRows($aspects, $allDefs, $articleType);
$counts = [
'required' => count(array_filter($aspects, static fn (array $a) => $a['required'])),
'recommended' => count(array_filter($aspects, static fn (array $a) => !$a['required'] && 'RECOMMENDED' === $a['usage'])),
'optional' => count(array_filter($aspects, static fn (array $a) => !$a['required'] && 'OPTIONAL' === $a['usage'])),
'required' => \count(array_filter($aspects, static fn (array $a) => $a['required'])),
'recommended' => \count(array_filter($aspects, static fn (array $a) => !$a['required'] && 'RECOMMENDED' === $a['usage'])),
'optional' => \count(array_filter($aspects, static fn (array $a) => !$a['required'] && 'OPTIONAL' === $a['usage'])),
];
}
return $this->render('admin/ebay/aspect_import.html.twig', [
'articleType' => $articleType,
'rows' => $rows,
'allDefs' => $allDefs,
'articleType' => $articleType,
'rows' => $rows,
'allDefs' => $allDefs,
'articleFields' => self::ARTICLE_FIELDS,
'counts' => $counts,
'categoryId' => $categoryId,
'searchUrl' => $this->generateUrl('admin_ebay_category_search'),
'counts' => $counts,
'categoryId' => $categoryId,
'searchUrl' => $this->generateUrl('admin_ebay_category_search'),
'existingMappings' => $articleType->getEbayMappings(),
]);
}
@ -158,7 +158,7 @@ final class EbayAspectImportController extends AbstractController
/** @var array<string, string> $data */
$data = array_map(static fn (mixed $v): string => \is_scalar($v) ? (string) $v : '', $rawData);
$action = $data['action'] ?? 'skip';
$action = $data['action'] ?? 'skip';
$ebayName = $data['ebayName'] ?? '';
$isRequired = ($data['ebayRequired'] ?? '0') === '1';
@ -193,7 +193,7 @@ final class EbayAspectImportController extends AbstractController
}
$rawValues = array_values(array_filter(array_map('trim', explode(',', $data['ebayValues'] ?? ''))));
$type = (count($rawValues) > 0 && count($rawValues) <= 30)
$type = (\count($rawValues) > 0 && \count($rawValues) <= 30)
? AttributeType::Select
: AttributeType::String;
@ -289,12 +289,12 @@ final class EbayAspectImportController extends AbstractController
$existing = $existingMappings[$aspect['name']];
$isFieldMapping = ArticleTypeEbayMapping::SOURCE_ARTICLE_FIELD === $existing->getSourceType();
$rows[] = [
'aspect' => $aspect,
'action' => $isFieldMapping ? 'article_field' : 'match',
'preMatchId' => $isFieldMapping ? null : $existing->getAttributeDefinition()?->getId()->toRfc4122(),
'preFieldKey' => $isFieldMapping ? $existing->getArticleFieldKey() : null,
'aspect' => $aspect,
'action' => $isFieldMapping ? 'article_field' : 'match',
'preMatchId' => $isFieldMapping ? null : $existing->getAttributeDefinition()?->getId()->toRfc4122(),
'preFieldKey' => $isFieldMapping ? $existing->getArticleFieldKey() : null,
'alreadyAssigned' => true,
'suggestedType' => 'string',
'suggestedType' => 'string',
];
continue;
}
@ -327,7 +327,7 @@ final class EbayAspectImportController extends AbstractController
$preMatchId = null;
}
$suggestedType = (count($aspect['values']) > 0 && count($aspect['values']) <= 30)
$suggestedType = (\count($aspect['values']) > 0 && \count($aspect['values']) <= 30)
? AttributeType::Select->value
: AttributeType::String->value;

View file

@ -18,12 +18,12 @@ use Symfony\Contracts\Translation\TranslatorInterface;
final class PipelineStreamController extends AbstractController
{
private const array STEP_KEYS = [
'vision' => 'pipeline.step.vision',
'vision' => 'pipeline.step.vision',
'specs_research' => 'pipeline.step.specs_research',
'json_coding' => 'pipeline.step.json_coding',
'draft_article' => 'pipeline.step.draft_article',
'ebay_text' => 'pipeline.step.ebay_text',
'validation' => 'pipeline.step.validation',
'json_coding' => 'pipeline.step.json_coding',
'draft_article' => 'pipeline.step.draft_article',
'ebay_text' => 'pipeline.step.ebay_text',
'validation' => 'pipeline.step.validation',
];
public function __construct(
@ -69,7 +69,7 @@ final class PipelineStreamController extends AbstractController
$status = $job->getStatus()->value;
$prev = $seen[$jobId] ?? null;
if ($prev !== null && $prev['step'] === $step && $prev['status'] === $status) {
if (null !== $prev && $prev['step'] === $step && $prev['status'] === $status) {
continue;
}
@ -105,31 +105,25 @@ final class PipelineStreamController extends AbstractController
$label = "Job #{$inv}";
$t = fn (string $key, array $p = []) => $this->translator->trans($key, $p, 'admin');
$stepLabel = $step !== null
$stepLabel = null !== $step
? $t(self::STEP_KEYS[$step] ?? $step)
: '';
$message = match (true) {
$status === AIPipelineJobStatus::Queued->value
=> $t('pipeline.event.queued', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Processing->value && $step === null
=> $t('pipeline.event.processing_start', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Processing->value
=> $t('pipeline.event.processing_step', ['%inv%' => $inv, '%step%' => $stepLabel]),
$status === AIPipelineJobStatus::Completed->value
=> $t('pipeline.event.completed', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Failed->value
=> $t('pipeline.event.failed', ['%inv%' => $inv, '%reason%' => $job->getErrorMessage() ?? '']),
$status === AIPipelineJobStatus::NeedsReview->value
=> $t('pipeline.event.needs_review', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Queued->value => $t('pipeline.event.queued', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Processing->value && null === $step => $t('pipeline.event.processing_start', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Processing->value => $t('pipeline.event.processing_step', ['%inv%' => $inv, '%step%' => $stepLabel]),
$status === AIPipelineJobStatus::Completed->value => $t('pipeline.event.completed', ['%inv%' => $inv]),
$status === AIPipelineJobStatus::Failed->value => $t('pipeline.event.failed', ['%inv%' => $inv, '%reason%' => $job->getErrorMessage() ?? '']),
$status === AIPipelineJobStatus::NeedsReview->value => $t('pipeline.event.needs_review', ['%inv%' => $inv]),
default => "{$label}: {$status}",
};
return [
'jobId' => $job->getId()->toRfc4122(),
'label' => $label,
'status' => $status,
'step' => $step,
'jobId' => $job->getId()->toRfc4122(),
'label' => $label,
'status' => $status,
'step' => $step,
'message' => $message,
];
}

View file

@ -62,7 +62,7 @@ final class PromptTemplateCrudController extends AbstractCrudController
$lines = ['Use <code>{{variableName}}</code> as placeholders. Known keys and their variables:'];
$lines[] = '<ul>';
foreach ($known as $key => $vars) {
$varList = $vars !== [] ? implode(', ', array_map(static fn (string $v) => "<code>{{$v}}</code>", $vars)) : '—';
$varList = [] !== $vars ? implode(', ', array_map(static fn (string $v) => "<code>{{$v}}</code>", $vars)) : '—';
$lines[] = "<li><strong>{$key}</strong>: {$varList}</li>";
}
$lines[] = '</ul>';

View file

@ -39,7 +39,7 @@ final class TranslationCrudController extends AbstractCrudController
public function configureFields(string $pageName): iterable
{
$readonly = $pageName === Crud::PAGE_EDIT;
$readonly = Crud::PAGE_EDIT === $pageName;
yield IdField::new('id')->hideOnForm()->hideOnIndex();

View file

@ -15,8 +15,8 @@ final class StringArrayType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder->addModelTransformer(new CallbackTransformer(
fn (?array $v) => implode("\n", $v ?? []),
fn (mixed $v) => '' === ($v ?? '') ? null : array_values(
static fn (?array $v) => implode("\n", $v ?? []),
static fn (mixed $v) => '' === ($v ?? '') ? null : array_values(
array_filter(array_map('trim', explode("\n", (string) $v)))
),
));

View file

@ -69,7 +69,7 @@ final class DraftArticleHandler
$article->setManufacturer((string) $vision['manufacturer']);
}
$correctedModelNumber = (string) ($job->getOutputData()['specs_research']['correctedModelNumber'] ?? '');
$modelNumber = $correctedModelNumber !== '' ? $correctedModelNumber : (string) ($vision['modelNumber'] ?? '');
$modelNumber = '' !== $correctedModelNumber ? $correctedModelNumber : (string) ($vision['modelNumber'] ?? '');
if ('' !== $modelNumber) {
$article->setModelNumber($modelNumber);
}

View file

@ -80,7 +80,7 @@ final class PhotoUploadHandler
attributes: $attributes,
condition: $inputData['condition'] ?? 'good',
inventoryNumber: $inputData['inventoryNumber'] ?? null,
serialNumber: $result['serial'] !== '' ? $result['serial'] : null,
serialNumber: '' !== $result['serial'] ? $result['serial'] : null,
));
return;

View file

@ -10,7 +10,9 @@ use Doctrine\ORM\EntityManagerInterface;
final class DoctrineTranslationRepository implements TranslationRepositoryInterface
{
public function __construct(private readonly EntityManagerInterface $em) {}
public function __construct(private readonly EntityManagerInterface $em)
{
}
public function findMapByLocaleAndDomain(string $locale, string $domain): array
{

View file

@ -16,10 +16,10 @@ final class PermissionVoter extends Voter
public const PREFIX = 'PERM_';
public const ARTICLES_MANAGE = 'articles.manage';
public const PIPELINE_RUN = 'pipeline.run';
public const ORDERS_MANAGE = 'orders.manage';
public const USERS_MANAGE = 'users.manage';
public const PROMPTS_MANAGE = 'prompts.manage';
public const PIPELINE_RUN = 'pipeline.run';
public const ORDERS_MANAGE = 'orders.manage';
public const USERS_MANAGE = 'users.manage';
public const PROMPTS_MANAGE = 'prompts.manage';
public const SETTINGS_MANAGE = 'settings.manage';
/** @return list<string> */

View file

@ -28,7 +28,7 @@ final class DatabaseTranslator implements TranslatorInterface, LocaleAwareInterf
$effectiveDomain = $domain ?? 'messages';
$cacheKey = $effectiveLocale.'.'.$effectiveDomain;
if (!array_key_exists($cacheKey, $this->cache)) {
if (!\array_key_exists($cacheKey, $this->cache)) {
try {
$this->cache[$cacheKey] = $this->repo->findMapByLocaleAndDomain($effectiveLocale, $effectiveDomain);
} catch (\Throwable) {
@ -36,10 +36,10 @@ final class DatabaseTranslator implements TranslatorInterface, LocaleAwareInterf
}
}
if (array_key_exists($id, $this->cache[$cacheKey])) {
if (\array_key_exists($id, $this->cache[$cacheKey])) {
$value = $this->cache[$cacheKey][$id];
return $parameters !== [] ? strtr($value, $parameters) : $value;
return [] !== $parameters ? strtr($value, $parameters) : $value;
}
return $this->inner->trans($id, $parameters, $domain, $locale);

View file

@ -56,7 +56,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
);
}
public function test_fetches_application_token(): void
public function testFetchesApplicationToken(): void
{
$token = $this->oauth->getAccessToken();
@ -64,7 +64,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertStringStartsWith('v^1.1', $token);
}
public function test_fetches_aspects_for_notebooks_category(): void
public function testFetchesAspectsForNotebooksCategory(): void
{
// 177 = Notebooks & Netbooks in EBAY_DE
$aspects = $this->taxonomy->getCategoryAspects('177');
@ -89,7 +89,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
);
}
public function test_three_tier_aspect_classification(): void
public function testThreeTierAspectClassification(): void
{
// eBay has three effective tiers:
// required=true + usage=RECOMMENDED → hard gate, eBay blocks listing without it
@ -98,15 +98,15 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$aspects = $this->taxonomy->getCategoryAspects('177');
$hardRequired = array_filter($aspects, static fn (array $a) => $a['required']);
$recommended = array_filter($aspects, static fn (array $a) => !$a['required'] && 'RECOMMENDED' === $a['usage']);
$optional = array_filter($aspects, static fn (array $a) => !$a['required'] && 'OPTIONAL' === $a['usage']);
$recommended = array_filter($aspects, static fn (array $a) => !$a['required'] && 'RECOMMENDED' === $a['usage']);
$optional = array_filter($aspects, static fn (array $a) => !$a['required'] && 'OPTIONAL' === $a['usage']);
$this->assertNotEmpty($hardRequired, 'Should have hard-required aspects');
$this->assertNotEmpty($recommended, 'Should have recommended (ranking-signal) aspects');
$this->assertNotEmpty($optional, 'Should have truly optional aspects');
}
public function test_aspects_with_predefined_values_have_options(): void
public function testAspectsWithPredefinedValuesHaveOptions(): void
{
$aspects = $this->taxonomy->getCategoryAspects('177');
@ -115,7 +115,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertNotEmpty($withOptions, 'Some aspects should have predefined selectable values');
}
public function test_caches_aspects_on_second_call(): void
public function testCachesAspectsOnSecondCall(): void
{
// First call hits the API
$first = $this->taxonomy->getCategoryAspects('177');
@ -125,7 +125,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertSame($first, $second);
}
public function test_fetches_aspects_for_ram_category(): void
public function testFetchesAspectsForRamCategory(): void
{
// 170083 = RAM/Speicher in EBAY_DE — useful for our memory article types
$aspects = $this->taxonomy->getCategoryAspects('170083');
@ -135,7 +135,7 @@ final class EbayTaxonomyIntegrationTest extends TestCase
$this->assertNotEmpty($names);
}
public function test_category_suggestions_returns_results(): void
public function testCategorySuggestionsReturnsResults(): void
{
$results = $this->taxonomy->getCategorySuggestions('Notebook');

View file

@ -49,7 +49,7 @@ final class FrappeCustomerIntegrationTest extends TestCase
}
}
public function test_create_customer(): void
public function testCreateCustomer(): void
{
$response = $this->client->post('/api/resource/Customer', [
'customer_name' => 'Test Superseller Integration',
@ -64,7 +64,7 @@ final class FrappeCustomerIntegrationTest extends TestCase
$this->createdCustomerName = $response['data']['name'];
}
public function test_find_created_customer(): void
public function testFindCreatedCustomer(): void
{
// Create first
$created = $this->client->post('/api/resource/Customer', [
@ -82,14 +82,14 @@ final class FrappeCustomerIntegrationTest extends TestCase
$this->assertSame('Test Superseller Integration', $response['data']['customer_name']);
}
public function test_find_nonexistent_customer_throws(): void
public function testFindNonexistentCustomerThrows(): void
{
$this->expectException(ClientExceptionInterface::class);
$this->client->get('/api/resource/Customer/CUST-DOES-NOT-EXIST-99999');
}
public function test_delete_customer(): void
public function testDeleteCustomer(): void
{
// Create
$created = $this->client->post('/api/resource/Customer', [

View file

@ -61,7 +61,7 @@ final class FrappeErpAdapterIntegrationTest extends TestCase
}
}
public function test_find_simon_kuehn_by_name_and_address(): void
public function testFindSimonKuehnByNameAndAddress(): void
{
$customerId = $this->adapter->findExistingCustomer(
'Simon Kühn',
@ -74,7 +74,7 @@ final class FrappeErpAdapterIntegrationTest extends TestCase
$this->assertStringStartsWith('Simon', $customerId);
}
public function test_unknown_person_is_not_found(): void
public function testUnknownPersonIsNotFound(): void
{
$customerId = $this->adapter->findExistingCustomer(
'Voldemort',
@ -86,7 +86,7 @@ final class FrappeErpAdapterIntegrationTest extends TestCase
$this->assertNull($customerId);
}
public function test_find_simon_and_create_invoice_for_1337(): void
public function testFindSimonAndCreateInvoiceFor1337(): void
{
$frappeId = $this->adapter->findExistingCustomer(
'Simon Kühn',

View file

@ -5,7 +5,6 @@ declare(strict_types=1);
namespace App\Tests\Unit\Domain\Article;
use App\Domain\Article\ArticleType;
use App\Domain\Article\ArticleTypeAttribute;
use App\Domain\Article\AttributeDefinition;
use App\Domain\Article\AttributeType;
use PHPUnit\Framework\TestCase;

View file

@ -35,8 +35,8 @@ final class AttributeDefinitionTest extends TestCase
public static function allAttributeTypes(): array
{
return array_combine(
array_map(fn (AttributeType $t) => $t->value, AttributeType::cases()),
array_map(fn (AttributeType $t) => [$t], AttributeType::cases()),
array_map(static fn (AttributeType $t) => $t->value, AttributeType::cases()),
array_map(static fn (AttributeType $t) => [$t], AttributeType::cases()),
);
}

View file

@ -87,7 +87,7 @@ final class ApiKeyTest extends TestCase
public function testRawKeyVerifiesAgainstStoredHash(): void
{
$rawKey = bin2hex(random_bytes(24));
$hash = password_hash($rawKey, PASSWORD_BCRYPT);
$hash = password_hash($rawKey, \PASSWORD_BCRYPT);
$prefix = substr($rawKey, 0, 8);
$key = new ApiKey($this->user, 'label', $prefix, $hash);

View file

@ -4,11 +4,11 @@ declare(strict_types=1);
namespace App\Tests\Unit\Infrastructure\AI\Agent;
use App\Domain\AI\PromptTemplate;
use App\Domain\AI\Repository\PromptTemplateRepositoryInterface;
use App\Domain\Article\ArticleType;
use App\Domain\Article\AttributeDefinition;
use App\Domain\Article\AttributeType;
use App\Domain\AI\PromptTemplate;
use App\Domain\AI\Repository\PromptTemplateRepositoryInterface;
use App\Infrastructure\AI\Agent\JsonCodingAgent;
use App\Infrastructure\AI\OllamaClientInterface;
use App\Infrastructure\AI\PromptTemplateService;

View file

@ -28,7 +28,7 @@ final class OllamaVisionAgentTest extends TestCase
$this->agent = new OllamaVisionAgent($this->ollama, $prompts, 'llava');
}
public function test_parses_all_fields(): void
public function testParsesAllFields(): void
{
$this->ollama->method('generateWithImage')->willReturn(
"MANUFACTURER: Lenovo\nMODEL_NAME: ThinkBook 14 G6 IRL\nMODEL_NUMBER: 21KG00NQGE\nSERIAL: PNV09SJZ"
@ -42,7 +42,7 @@ final class OllamaVisionAgentTest extends TestCase
$this->assertSame('PNV09SJZ', $result['serial']);
}
public function test_strips_serial_bleed_into_model_number_leading(): void
public function testStripsSerialBleedIntoModelNumberLeading(): void
{
// LLM outputs "SERIAL: xyz" as the entire value for MODEL_NUMBER
$this->ollama->method('generateWithImage')->willReturn(
@ -55,7 +55,7 @@ final class OllamaVisionAgentTest extends TestCase
$this->assertSame('1005NK677594', $result['serial']);
}
public function test_strips_serial_bleed_into_model_name_mid_value(): void
public function testStripsSerialBleedIntoModelNameMidValue(): void
{
// LLM appends serial after the model name
$this->ollama->method('generateWithImage')->willReturn(
@ -68,7 +68,7 @@ final class OllamaVisionAgentTest extends TestCase
$this->assertSame('PNV09SJZ', $result['serial']);
}
public function test_returns_empty_strings_when_fields_missing(): void
public function testReturnsEmptyStringsWhenFieldsMissing(): void
{
$this->ollama->method('generateWithImage')->willReturn('I cannot read the nameplate.');

View file

@ -49,7 +49,7 @@ final class MistralClientTest extends TestCase
public function testGenerateWithImageReturnsParsedContent(): void
{
$tmpFile = tempnam(sys_get_temp_dir(), 'test_') . '.jpg';
$tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.jpg';
file_put_contents($tmpFile, 'fake-image-data');
$body = json_encode([
@ -66,7 +66,7 @@ final class MistralClientTest extends TestCase
public function testGenerateWithImageEncodesImageAsBase64(): void
{
$tmpFile = tempnam(sys_get_temp_dir(), 'test_') . '.png';
$tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.png';
$imageContent = 'fake-png-bytes';
file_put_contents($tmpFile, $imageContent);
@ -92,7 +92,7 @@ final class MistralClientTest extends TestCase
*/
public function testGuessMimeTypeFromExtension(string $filename, string $expectedMime): void
{
$tmpFile = tempnam(sys_get_temp_dir(), 'test_') . '.' . pathinfo($filename, PATHINFO_EXTENSION);
$tmpFile = tempnam(sys_get_temp_dir(), 'test_').'.'.pathinfo($filename, \PATHINFO_EXTENSION);
file_put_contents($tmpFile, 'x');
$body = json_encode(['choices' => [['message' => ['content' => 'ok']]]]);
@ -105,7 +105,7 @@ final class MistralClientTest extends TestCase
$requestBody = json_decode($response->getRequestOptions()['body'], true);
$imageUrl = $requestBody['messages'][0]['content'][1]['image_url']['url'];
self::assertStringStartsWith('data:' . $expectedMime . ';base64,', $imageUrl);
self::assertStringStartsWith('data:'.$expectedMime.';base64,', $imageUrl);
}
/** @return array<string, array{string, string}> */

View file

@ -33,7 +33,7 @@ final class ArticleTypeCrudControllerTest extends TestCase
public function testConfigureFieldsContainsExpectedProperties(): void
{
$fields = iterator_to_array($this->controller->configureFields('new'));
$names = array_map(fn ($f) => $f->getAsDto()->getProperty(), $fields);
$names = array_map(static fn ($f) => $f->getAsDto()->getProperty(), $fields);
self::assertContains('name', $names);
self::assertContains('requiredAttributeDefs', $names);
@ -43,7 +43,7 @@ final class ArticleTypeCrudControllerTest extends TestCase
public function testConfigureFieldsForIndexContainsNameAndCount(): void
{
$fields = iterator_to_array($this->controller->configureFields('index'));
$names = array_map(fn ($f) => $f->getAsDto()->getProperty(), $fields);
$names = array_map(static fn ($f) => $f->getAsDto()->getProperty(), $fields);
self::assertContains('name', $names);
self::assertContains('attributeAssignments', $names);

View file

@ -36,7 +36,7 @@ final class AttributeDefinitionCrudControllerTest extends TestCase
public function testConfigureFieldsYieldsExpectedFieldNames(): void
{
$fields = iterator_to_array($this->controller->configureFields('new'));
$names = array_map(fn ($f) => $f->getAsDto()->getProperty(), $fields);
$names = array_map(static fn ($f) => $f->getAsDto()->getProperty(), $fields);
self::assertContains('name', $names);
self::assertContains('type', $names);

View file

@ -19,7 +19,7 @@ final class EbayWebhookVerifierTest extends TestCase
);
}
public function test_valid_signature_passes(): void
public function testValidSignaturePasses(): void
{
$body = '{"notification":{"data":{"orderId":"123"}}}';
$expected = base64_encode(hash('sha256', $body.'my-secret-tokenhttps://example.com/webhooks/ebay', binary: true));
@ -27,12 +27,12 @@ final class EbayWebhookVerifierTest extends TestCase
$this->assertTrue($this->verifier->verify($body, $expected));
}
public function test_invalid_signature_fails(): void
public function testInvalidSignatureFails(): void
{
$this->assertFalse($this->verifier->verify('{"body":"x"}', 'invalidsignature'));
}
public function test_challenge_response_returns_correct_hash(): void
public function testChallengeResponseReturnsCorrectHash(): void
{
$challengeCode = 'abc123';
$expected = hash('sha256', $challengeCode.'my-secret-tokenhttps://example.com/webhooks/ebay');

View file

@ -26,7 +26,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->adapter = new FrappeErpAdapter($this->frappe, 'REFURB-HW');
}
public function test_create_customer_returns_frappe_id(): void
public function testCreateCustomerReturnsFrappeId(): void
{
$this->frappe
->method('post')
@ -41,7 +41,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertSame('CUST-00001', $result);
}
public function test_create_sales_invoice_submits_and_returns_id(): void
public function testCreateSalesInvoiceSubmitsAndReturnsId(): void
{
$this->frappe
->expects($this->once())
@ -71,7 +71,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertSame('SINV-00001', $result);
}
public function test_fetch_invoice_pdf_returns_binary(): void
public function testFetchInvoicePdfReturnsBinary(): void
{
$this->frappe
->method('getContent')
@ -83,7 +83,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertStringStartsWith('%PDF', $result);
}
public function test_find_existing_customer_returns_id_when_address_matches(): void
public function testFindExistingCustomerReturnsIdWhenAddressMatches(): void
{
$this->frappe
->expects($this->exactly(2))
@ -98,7 +98,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertSame('CUST-99999', $result);
}
public function test_find_existing_customer_returns_null_when_address_mismatch(): void
public function testFindExistingCustomerReturnsNullWhenAddressMismatch(): void
{
$this->frappe
->expects($this->exactly(2))
@ -113,7 +113,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertNull($result);
}
public function test_find_existing_customer_returns_null_when_not_in_erp(): void
public function testFindExistingCustomerReturnsNullWhenNotInErp(): void
{
$this->frappe
->expects($this->once())
@ -125,7 +125,7 @@ final class FrappeErpAdapterTest extends TestCase
$this->assertNull($result);
}
public function test_find_simon_and_create_invoice_for_1337(): void
public function testFindSimonAndCreateInvoiceFor1337(): void
{
$this->frappe
->expects($this->exactly(2))

View file

@ -69,7 +69,7 @@ final class CreateApiKeyCommandTest extends TestCase
$savedKey = null;
$this->apiKeys->expects($this->once())
->method('save')
->willReturnCallback(function (ApiKey $key) use (&$savedKey): void {
->willReturnCallback(static function (ApiKey $key) use (&$savedKey): void {
$savedKey = $key;
});
@ -84,7 +84,7 @@ final class CreateApiKeyCommandTest extends TestCase
self::assertNotNull($savedKey);
self::assertSame('dev laptop', $savedKey->getLabel());
self::assertSame(8, strlen($savedKey->getKeyPrefix()));
self::assertSame(8, \strlen($savedKey->getKeyPrefix()));
self::assertStringContainsString($savedKey->getKeyPrefix(), $display);
}
@ -95,7 +95,7 @@ final class CreateApiKeyCommandTest extends TestCase
$savedKey = null;
$this->apiKeys->method('save')
->willReturnCallback(function (ApiKey $key) use (&$savedKey): void {
->willReturnCallback(static function (ApiKey $key) use (&$savedKey): void {
$savedKey = $key;
});

View file

@ -24,7 +24,7 @@ final class CustomerResolverTest extends TestCase
$this->resolver = new CustomerResolver($this->customerRepo, $this->erp);
}
public function test_stage_1_platform_id_match_returns_existing_customer_without_erp_call(): void
public function testStage1PlatformIdMatchReturnsExistingCustomerWithoutErpCall(): void
{
$existing = new Customer('Max Mustermann', 'max@test.de', ['street' => 'Musterstr 1', 'city' => 'Berlin', 'zip' => '10115']);
$existing->addPlatformId('ebay', 'buyer123');
@ -46,7 +46,7 @@ final class CustomerResolverTest extends TestCase
$this->assertSame($existing, $result);
}
public function test_stage_2_address_match_adds_platform_id_and_saves(): void
public function testStage2AddressMatchAddsPlatformIdAndSaves(): void
{
$existing = new Customer('Max Mustermann', 'max@test.de', ['street' => 'Musterstr 1', 'city' => 'Berlin', 'zip' => '10115']);
@ -69,7 +69,7 @@ final class CustomerResolverTest extends TestCase
$this->assertSame('buyer456', $result->getPlatformId('ebay'));
}
public function test_no_match_creates_new_customer_via_erp(): void
public function testNoMatchCreatesNewCustomerViaErp(): void
{
$this->customerRepo->method('findByPlatformId')->willReturn(null);
$this->customerRepo->method('findByMatchingKey')->willReturn(null);