Passed
Push — master ( 384538...4f7dc7 )
by Anton
02:27
created

OptionSchema::getOption()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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