Lines 100.00% 49 / 49
Functions and Methods 100.00% 7 / 7
Classes and Traits 100.00% 1 / 1
Name Lines Functions and Methods CRAP Classes and Traits
ServiceMethodEmitter 100.00% 49 / 49 100.00% 7 / 7 29 100.00% 1 / 1
 addHandleMethod 100.00% 8 / 8 100.00% 1 / 1 3
 addDomainServiceMethod 100.00% 12 / 12 100.00% 1 / 1 5
 declareMethod 100.00% 5 / 5 100.00% 1 / 1 3
 addFieldParameters 100.00% 4 / 4 100.00% 1 / 1 3
 buildDocblock 100.00% 13 / 13 100.00% 1 / 1 9
 buildPayload 100.00% 4 / 4 100.00% 1 / 1 2
 listReservedMemberNames 100.00% 3 / 3 100.00% 1 / 1 4
1<?php
2
3declare(strict_types=1);
4
5namespace Stewart\Codegen\Emitter\Service;
6
7use Nette\PhpGenerator\ClassType;
8use Nette\PhpGenerator\Method;
9use Stewart\Codegen\Php\MemberScope;
10use Stewart\Codegen\Php\ReservesMemberNames;
11use Stewart\Codegen\Service\ServiceModel;
12use Stewart\Contracts\Service\ServiceResponse;
13use Stewart\Contracts\Service\ServiceTargetSource;
14use Stewart\Contracts\State\EventContext;
15
16final readonly class ServiceMethodEmitter implements ReservesMemberNames
17{
18    private const string TARGET_PARAMETER = 'target';
19
20    private const array RESERVED_PARAMETER_NAMES = ['this'];
21
22    private const array RESERVED_TARGETED_PARAMETER_NAMES = [...self::RESERVED_PARAMETER_NAMES, self::TARGET_PARAMETER];
23
24    public function addHandleMethod(ClassType $class, ServiceModel $service): void
25    {
26        $method = $this->declareMethod($class, $service);
27        $this->addFieldParameters($method, $service);
28
29        $method->setBody(\sprintf(
30            'return $this->entity->%s(%s%s);',
31            $service->definition->returnsResponse ? 'callServiceForResponse' : 'callService',
32            var_export($service->definition->name, true),
33            $service->fields === [] ? '' : ', ' . $this->buildPayload($service),
34        ));
35    }
36
37    public function addDomainServiceMethod(ClassType $class, ServiceModel $service): void
38    {
39        $method = $this->declareMethod($class, $service);
40
41        if ($service->definition->target->isTargetable) {
42            $method->addParameter(self::TARGET_PARAMETER)->setType(ServiceTargetSource::class);
43        }
44
45        $this->addFieldParameters($method, $service);
46
47        $method->setBody(\sprintf(
48            'return $this->ha->%s(%s, %s, %s%s);',
49            $service->definition->returnsResponse ? 'callServiceForResponse' : 'callService',
50            var_export($service->definition->domain, true),
51            var_export($service->definition->name, true),
52            $service->fields === [] ? '[]' : $this->buildPayload($service),
53            $service->definition->target->isTargetable ? ', $' . self::TARGET_PARAMETER : '',
54        ));
55    }
56
57    private function declareMethod(ClassType $class, ServiceModel $service): Method
58    {
59        $method = $class->addMethod($service->method)
60            ->setReturnType($service->definition->returnsResponse ? ServiceResponse::class : EventContext::class);
61
62        foreach ($this->buildDocblock($service) as $line) {
63            $method->addComment($line);
64        }
65
66        return $method;
67    }
68
69    private function addFieldParameters(Method $method, ServiceModel $service): void
70    {
71        foreach ($service->fields as $field) {
72            $parameter = $method->addParameter($field->parameter)->setType($field->type()->native);
73
74            if (!$field->field->required) {
75                $parameter->setDefaultValue(null);
76            }
77        }
78    }
79
80    /** @return list<string> */
81    private function buildDocblock(ServiceModel $service): array
82    {
83        $title = $service->definition->title;
84        $description = $service->definition->description;
85        $lines = $title === null ? [] : [$title];
86
87        if ($description !== null) {
88            $lines = [...$lines, ...($lines === [] ? [] : ['']), $description];
89        }
90
91        $params = [];
92
93        foreach ($service->fields as $field) {
94            $summary = $field->summary();
95
96            if (!$field->type()->needsDocblock() && $summary === '') {
97                continue;
98            }
99
100            $params[] = rtrim(\sprintf('@param %s $%s %s', $field->type()->docblock, $field->parameter, $summary));
101        }
102
103        if ($lines !== [] && $params !== []) {
104            $lines[] = '';
105        }
106
107        return [...$lines, ...$params];
108    }
109
110    private function buildPayload(ServiceModel $service): string
111    {
112        $entries = '';
113
114        foreach ($service->fields as $field) {
115            $entries .= \sprintf("    %s => $%s,\n", var_export($field->field->name, true), $field->parameter);
116        }
117
118        return \sprintf("[\n%s]", $entries);
119    }
120
121    public function listReservedMemberNames(MemberScope $scope): array
122    {
123        return match ($scope) {
124            MemberScope::Parameters => self::RESERVED_PARAMETER_NAMES,
125            MemberScope::TargetedParameters => self::RESERVED_TARGETED_PARAMETER_NAMES,
126            default => [],
127        };
128    }
129}