Passed
Push — master ( ff3639...8a879c )
by Anton
01:54
created

OptionSchema::get()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 11
nc 7
nop 1
dl 0
loc 24
rs 8.8333
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Cycle\Schema\Relation;
10
11
use Cycle\Schema\Exception\OptionException;
12
13
/**
14
 * Calculate missing option values using template and relation context.
15
 */
16
final class OptionSchema
17
{
18
    /** @var array */
19
    private $aliases = [];
20
21
    /** @var array */
22
    private $options = [];
23
24
    /** @var array */
25
    private $template = [];
26
27
    /** @var array */
28
    private $context = [];
29
30
    /**
31
     * @param array $aliases
32
     */
33
    public function __construct(array $aliases)
34
    {
35
        $this->aliases = $aliases;
36
    }
37
38
    /**
39
     * Create new option set with user provided options.
40
     *
41
     * @param iterable $options
42
     * @return OptionSchema
43
     */
44
    public function withOptions(iterable $options): self
45
    {
46
        $r = clone $this;
47
        foreach ($options as $name => $value) {
48
            if (!isset($r->aliases[$name]) && !isset($r->template[$name])) {
49
                throw new OptionException("Undefined relation option `{$name}`");
50
            }
51
52
            $r->options[$name] = $value;
53
        }
54
55
        return $r;
56
    }
57
58
    /**
59
     * Create new option set with option rendering template. Template expect to allocate
60
     * relation options only in a integer constants.
61
     *
62
     * @param array $template
63
     * @return OptionSchema
64
     */
65
    public function withTemplate(array $template): self
66
    {
67
        $r = clone $this;
68
        $r->template = $template;
69
70
        return $r;
71
    }
72
73
    /**
74
     * Create new option set with relation context values (i.e. relation name, target name and etc).
75
     *
76
     * @param array $context
77
     * @return OptionSchema
78
     */
79
    public function withContext(array $context): self
80
    {
81
        $r = clone $this;
82
        $r->context += $context;
83
84
        return $r;
85
    }
86
87
    /**
88
     * Check if option has been defined.
89
     *
90
     * @param int $option
91
     * @return bool
92
     */
93
    public function has(int $option): bool
94
    {
95
        return array_key_exists($option, $this->template);
96
    }
97
98
    /**
99
     * Get calculated option value.
100
     *
101
     * @param int $option
102
     * @return mixed
103
     */
104
    public function get(int $option)
105
    {
106
        if (!$this->has($option)) {
107
            throw new OptionException("Undefined relation option `{$option}`");
108
        }
109
110
        if (isset($this->options[$option])) {
111
            return $this->options[$option];
112
        }
113
114
        // user defined value
115
        foreach ($this->aliases as $alias => $targetOption) {
116
            if ($targetOption === $option && isset($this->options[$alias])) {
117
                return $this->options[$alias];
118
            }
119
        }
120
121
        // non template value
122
        $value = $this->template[$option];
123
        if (!is_string($value)) {
124
            return $value;
125
        }
126
127
        return $this->calculate($option, $value);
128
    }
129
130
    /**
131
     * @return array
132
     */
133
    public function __debugInfo()
134
    {
135
        $result = [];
136
137
        foreach ($this->template as $option => $value) {
138
            $value = $this->get($option);
139
140
            $alias = array_search($option, $this->aliases, true);
141
            $result[$alias] = $value;
142
        }
143
144
        return $result;
145
    }
146
147
    /**
148
     * Calculate option value using templating.
149
     *
150
     * @param int    $option
151
     * @param string $value
152
     * @return string
153
     */
154
    private function calculate(int $option, string $value): string
155
    {
156
        foreach ($this->context as $name => $ctxValue) {
157
            $value = $this->injectValue($name, $ctxValue, $value);
158
        }
159
160
        foreach ($this->aliases as $name => $targetOption) {
161
            if ($option !== $targetOption) {
162
                $value = $this->injectOption($name, $targetOption, $value);
163
            }
164
        }
165
166
        return $value;
167
    }
168
169
    /**
170
     * @param string $name
171
     * @param int    $option
172
     * @param string $target
173
     * @return string
174
     */
175
    private function injectOption(string $name, int $option, string $target): string
176
    {
177
        if (strpos($target, "{{$name}}") === false) {
178
            return $target;
179
        }
180
181
        return str_replace("{{$name}}", $this->get($option), $target);
182
    }
183
184
    /**
185
     * @param string $name
186
     * @param string $value
187
     * @param string $target
188
     * @return string
189
     */
190
    private function injectValue(string $name, string $value, string $target): string
191
    {
192
        return str_replace("{{$name}}", $value, $target);
193
    }
194
}