Lines 98.38% 61 / 62
Methods 85.71% 6 / 7
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 __construct 0.00% 0 / 1 0.00% 0 / 1 2
 buildModelFromSnapshot 100.00% 35 / 35 100.00% 1 / 1 2
 sortedDomainNames 100.00% 3 / 3 100.00% 1 / 1 1
 buildAttributeModels 100.00% 6 / 6 100.00% 1 / 1 2
 buildServiceModels 100.00% 9 / 9 100.00% 1 / 1 2
 selectParameterScope 100.00% 3 / 3 100.00% 1 / 1 3
 buildFieldModels 100.00% 5 / 5 100.00% 1 / 1 2
38final readonly class GenerationModelFactory
39{
40    public function __construct(
41        private AttributeKindInference $attributeKinds,
42        private EntitySelector $entitySelector,
43        private AttributeSelector $attributeSelector,
44        private ServiceCatalogParser $serviceCatalogParser,
45        private MemberReservations $memberReservations,
46        private DomainCatalog $domainCatalog,
47    ) {}
48
49    /** @throws CodegenException */
50    public function buildModelFromSnapshot(Snapshot $snapshot, GenerationOptions $options): GenerationModel
51    {
52        $selection = $this->entitySelector->selectEntities($snapshot, $options->inclusionRules);
53        $catalog = $this->serviceCatalogParser->parseServicesResponse($snapshot->services);
54        $byDomain = $selection->generated->groupByDomain();
55
56        $accessors = $this->memberReservations->createAllocatorFor(MemberScope::RootClasses);
57        $classStems = $this->memberReservations->createAllocatorFor(MemberScope::ClassStems);
58        $domains = [];
59        $unseenAttributes = [];
60
61        foreach ($this->sortedDomainNames($selection->generated, $catalog) as $domain) {
62            $states = $byDomain[$domain] ?? EntityStateCollection::empty();
63            $attributes = $this->attributeSelector->selectAttributes($domain, $this->attributeKinds->inferKindsByAttributeName($states), $options->attributesFor($domain));
64            $unseenAttributes = [...$unseenAttributes, ...$attributes->unseen];
65
66            $domains[] = new DomainModel(
67                domain: $domain,
68                accessor: $accessors->claim(Identifier::convertToCamelCase($domain)),
69                classStem: $classStems->claim(Identifier::convertToPascalCase($domain)),
70                traits: $this->domainCatalog->getTraitsForDomain($domain),
71                entityIds: $states->sortedByEntityId()->listEntityIds(),
72                attributes: $this->buildAttributeModels($attributes->typed),
73                handleServices: $this->buildServiceModels($catalog->forEntityHandle($domain), MemberScope::EntityHandle),
74                services: $this->buildServiceModels($catalog->forDomain($domain), MemberScope::DomainServices),
75            );
76        }
77
78        return new GenerationModel(
79            domains: DomainModelCollection::fromList($domains),
80            entityIds: $selection->listEntityIds(),
81            ignoredEntityIds: $selection->ignored,
82            warnings: [
83                ...$options->inclusionRules->listUnmatchedIncludes($snapshot->states)->mapToList(
84                    static fn(Selector $include): UnmatchedEntitySelector => new UnmatchedEntitySelector($include->getPattern()),
85                ),
86                ...$unseenAttributes,
87            ],
88            areaIds: AreaIdCollection::fromList($snapshot->areas->mapToList(static fn(Area $area): AreaId => $area->areaId)),
89            floorIds: FloorIdCollection::fromList($snapshot->floors->mapToList(static fn(Floor $floor): FloorId => $floor->floorId)),
90            labelIds: LabelIdCollection::fromList($snapshot->labels->mapToList(static fn(Label $label): LabelId => $label->labelId)),
91        );
92    }
93
94    /** @return list<string> */
95    private function sortedDomainNames(EntityStateCollection $states, ServiceCatalog $catalog): array
96    {
97        $domains = array_values(array_unique([...$states->listDomains(), ...$catalog->listDomains()]));
98        sort($domains);
99
100        return $domains;
101    }
102
103    /**
104     * @param array<string, AttributeKind> $typed
105     * @return list<AttributeModel>
106     */
107    private function buildAttributeModels(array $typed): array
108    {
109        $names = $this->memberReservations->createAllocatorFor(MemberScope::StateView);
110        $attributes = [];
111
112        foreach ($typed as $key => $kind) {
113            $key = (string) $key;
114            $attributes[] = new AttributeModel($names->claim('get' . ucfirst(Identifier::convertToCamelCase($key))), $key, $kind);
115        }
116
117        return $attributes;
118    }
119
120    /** @return list<ServiceModel> */
121    private function buildServiceModels(ServiceDefinitionCollection $definitions, MemberScope $methodScope): array
122    {
123        $names = $this->memberReservations->createAllocatorFor($methodScope);
124        $services = [];
125
126        foreach ($definitions as $definition) {
127            $services[] = new ServiceModel(
128                $names->claim(Identifier::convertToCamelCase($definition->name)),
129                $definition,
130                $this->buildFieldModels($definition, $this->selectParameterScope($methodScope, $definition)),
131            );
132        }
133
134        return $services;
135    }
136
137    private function selectParameterScope(MemberScope $methodScope, ServiceDefinition $definition): MemberScope
138    {
139        return $methodScope === MemberScope::DomainServices && $definition->target->isTargetable
140            ? MemberScope::TargetedParameters
141            : MemberScope::Parameters;
142    }
143
144    /** @return list<FieldModel> */
145    private function buildFieldModels(ServiceDefinition $definition, MemberScope $parameterScope): array
146    {
147        $names = $this->memberReservations->createAllocatorFor($parameterScope);
148        $models = [];
149
150        foreach ($definition->fields as $field) {
151            $models[] = new FieldModel($names->claim(Identifier::convertToCamelCase($field->name)), $field);
152        }
153
154        return $models;
155    }
156}