OptionSchema::get()   B
last analyzed

Complexity

Conditions 8
Paths 9

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 8

Importance

Changes 0
Metric Value
cc 8
eloc 14
nc 9
nop 1
dl 0
loc 30
ccs 11
cts 11
cp 1
crap 8
rs 8.4444
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cycle\Schema\Relation;
6
7
use Cycle\Schema\Exception\OptionException;
8
9
/**
10
 * Calculate missing option values using template and relation context.
11
 */
12
final class OptionSchema
13
{
14
    private array $options = [];
15
    private array $template = [];
16
    private array $context = [];
17
18
    public function __construct(private array $aliases) {}
19
20 1218
    /**
21
     * Get available options
22 1218
     */
23
    public function getOptions(): array
24 2
    {
25
        return $this->options;
26 2
    }
27
28 2
    /**
29 2
     * Create new option set with user provided options.
30
     */
31 2
    public function withOptions(iterable $options): self
32 2
    {
33
        $r = clone $this;
34
35 2
        foreach ($options as $name => $value) {
36
            if (!array_key_exists($name, $r->aliases) && !array_key_exists($name, $r->template)) {
37
                throw new OptionException("Undefined relation option `{$name}`");
38
            }
39
40
            $r->options[$name] = $value;
41 784
        }
42
43 784
        return $r;
44
    }
45
46
    /**
47
     * Create new option set with option rendering template. Template expect to allocate
48
     * relation options only in a integer constants.
49 1176
     */
50
    public function withTemplate(array $template): self
51 1176
    {
52
        $r = clone $this;
53 1176
        $r->template = $template;
54 632
55 2
        return $r;
56
    }
57
58 630
    /**
59
     * Create new option set with relation context values (i.e. relation name, target name and etc).
60
     */
61 1174
    public function withContext(array $context): self
62
    {
63
        $r = clone $this;
64
        $r->context += $context;
65
66
        return $r;
67
    }
68 1194
69
    /**
70 1194
     * Check if option has been defined.
71 1194
     */
72
    public function has(int $option): bool
73 1194
    {
74
        return array_key_exists($option, $this->template);
75
    }
76
77
    /**
78
     * Get calculated option value.
79 1168
     */
80
    public function get(int $option): mixed
81 1168
    {
82 1168
        if (!$this->has($option)) {
83
            throw new OptionException("Undefined relation option `{$option}`");
84 1168
        }
85
86
        if (array_key_exists($option, $this->options)) {
87
            return $this->options[$option];
88
        }
89
90 1088
        // user defined value
91
        foreach ($this->aliases as $alias => $targetOption) {
92 1088
            if ($targetOption === $option && isset($this->options[$alias])) {
93
                return $this->options[$alias];
94
            }
95
        }
96
97
        // non template value
98 1088
        $value = $this->template[$option];
99
        if (!is_string($value)) {
100 1088
            return $value;
101 2
        }
102
103
        $value = $this->calculate($option, $value);
104 1086
105 160
        if (strpos($value, '|') !== false) {
106
            return array_filter(explode('|', $value));
107
        }
108
109 1086
        return $value;
110 1076
    }
111 484
112
    public function __debugInfo(): array
113
    {
114
        $result = [];
115
116 1052
        foreach ($this->template as $option => $value) {
117 1052
            $value = $this->get($option);
118 974
119
            $alias = array_search($option, $this->aliases, true);
120
            $result[$alias] = $value;
121 1030
        }
122
123 1030
        return $result;
124 976
    }
125
126
    /**
127 670
     * Calculate option value using templating.
128
     */
129
    private function calculate(int $option, string $value): string
130
    {
131
        foreach ($this->context as $name => $ctxValue) {
132
            $ctxValue = is_array($ctxValue) ? implode('|', $ctxValue) . '|' : $ctxValue;
133 1030
            $value = $this->injectValue($name, $ctxValue, $value);
134
        }
135 1030
136 1024
        foreach ($this->aliases as $name => $targetOption) {
137 1024
            if ($option !== $targetOption) {
138
                $value = $this->injectOption($name, $targetOption, $value);
139
            }
140 1030
        }
141 1024
142 1024
        return $value;
143
    }
144
145
    private function injectOption(string $name, int $option, string $target): string
146 1030
    {
147
        if (!str_contains($target, "{{$name}}")) {
148
            return $target;
149 1024
        }
150
151 1024
        $name = "{{$name}}";
152 1024
        $replace = $this->get($option);
153
        if (is_array($replace)) {
154
            return implode('|', array_map(static function (string $replace) use ($name, $target) {
155 720
                return str_replace($name, $replace, $target);
156 720
            }, $replace));
157 720
        }
158 720
159 360
        return str_replace($name, $replace, $target);
160 720
    }
161
162
    private function injectValue(string $name, string $value, string $target): string
163
    {
164
        return str_replace("{{$name}}", $value, $target);
165
    }
166
}
167