Lines 100.00% 16 / 16
Functions and Methods 100.00% 3 / 3
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
AttributeSelector 100.00% 16 / 16 100.00% 3 / 3 10 100.00% 1 / 1
 __construct 100.00% 1 / 1 100.00% 1 / 1 1
 selectAttributes 100.00% 14 / 14 100.00% 1 / 1 6
 isTypeable 100.00% 1 / 1 100.00% 1 / 1 3
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Codegen\Attribute;
6
7use Stewart\Codegen\Php\Identifier;
8
9final readonly class AttributeSelector
10{
11    private const string FRIENDLY_NAME = 'friendly_name';
12
13    public function __construct(private AttributeCatalog $attributeCatalog) {}
14
15    /** @param array<string, AttributeKind> $observed */
16    public function selectAttributes(string $domain, array $observed, AttributeFilter $filter): AttributeSelection
17    {
18        $typed = $this->attributeCatalog->listKindsForDomain($domain);
19
20        foreach ($observed as $key => $observedKind) {
21            $key = (string) $key;
22            $kind = $this->attributeCatalog->findKind($domain, $key) ?? ($filter->includes($key) ? $observedKind : null);
23
24            if ($kind !== null) {
25                $typed[$key] = $kind;
26            }
27        }
28
29        $unseen = [];
30
31        foreach ($filter->include->exactEntries() as $key) {
32            if (!isset($observed[$key])) {
33                $typed[$key] ??= $this->attributeCatalog->findKind($domain, $key) ?? AttributeKind::Mixed;
34                $unseen[] = new UnseenAttribute($domain, $key);
35            }
36        }
37
38        $typed = array_filter($typed, fn(string|int $key): bool => $this->isTypeable((string) $key, $filter), \ARRAY_FILTER_USE_KEY);
39        ksort($typed, \SORT_STRING);
40
41        return new AttributeSelection($typed, $unseen);
42    }
43
44    // friendly_name has its own accessor; an include glob can match keys that are not identifiers.
45    private function isTypeable(string $key, AttributeFilter $filter): bool
46    {
47        return $key !== self::FRIENDLY_NAME && Identifier::startsWithLetter($key) && !$filter->excludes($key);
48    }
49}