Passed
Push — master ( e74e6b...b9a837 )
by Anton
01:37
created

OptionSchema   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 165
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 41
dl 0
loc 165
rs 10
c 0
b 0
f 0
wmc 20

10 Methods

Rating   Name   Duplication   Size   Complexity  
A injectOption() 0 7 2
A withTemplate() 0 6 1
A calculate() 0 13 4
A withOptions() 0 12 4
A __debugInfo() 0 12 2
A withContext() 0 6 1
A get() 0 12 3
A __construct() 0 3 1
A injectValue() 0 3 1
A has() 0 3 1
1
<?php
2
declare(strict_types=1);
3
/**
4
 * Spiral Framework.
5
 *
6
 * @license   MIT
7
 * @author    Anton Titov (Wolfy-J)
8
 */
9
10
namespace Cycle\Schema\Relation\Util;
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
        foreach ($options as $name => $value) {
49
            if (isset($r->aliases[$name]) && isset($r->template[$name])) {
50
                throw new OptionException("Undefined relation option `{$name}`");
51
            }
52
53
            $r->options[$name] = $value;
54
        }
55
56
        return $r;
57
    }
58
59
    /**
60
     * Create new option set with option rendering template. Template expect to allocate
61
     * relation options only in a integer constants.
62
     *
63
     * @param array $template
64
     * @return OptionSchema
65
     */
66
    public function withTemplate(array $template): self
67
    {
68
        $r = clone $this;
69
        $r->template = $template;
70
71
        return $r;
72
    }
73
74
    /**
75
     * Create new option set with relation context values (i.e. relation name, target name and etc).
76
     *
77
     * @param array $context
78
     * @return OptionSchema
79
     */
80
    public function withContext(array $context): self
81
    {
82
        $r = clone $this;
83
        $r->context += $context;
84
85
        return $r;
86
    }
87
88
    /**
89
     * Check if option has been defined.
90
     *
91
     * @param int $option
92
     * @return bool
93
     */
94
    public function has(int $option): bool
95
    {
96
        return isset($this->template[$option]);
97
    }
98
99
    /**
100
     * Get calculated option value.
101
     *
102
     * @param int $option
103
     * @return mixed
104
     */
105
    public function get(int $option)
106
    {
107
        if (!$this->has($option)) {
108
            throw new OptionException("Undefined relation option `{$option}`");
109
        }
110
111
        $value = $this->template[$option];
112
        if (!is_string($value)) {
113
            return $value;
114
        }
115
116
        return $this->calculate($option, $value);
117
    }
118
119
    /**
120
     * @return array
121
     */
122
    public function __debugInfo()
123
    {
124
        $result = [];
125
126
        foreach ($this->template as $option => $value) {
127
            $value = $this->get($option);
128
129
            $alias = array_search($option, $this->aliases, true);
130
            $result[$alias] = $value;
131
        }
132
133
        return $result;
134
    }
135
136
    /**
137
     * Calculate option value using templating.
138
     *
139
     * @param int    $option
140
     * @param string $value
141
     * @return string
142
     */
143
    private function calculate(int $option, string $value): string
144
    {
145
        foreach ($this->context as $name => $ctxValue) {
146
            $value = $this->injectValue($name, $ctxValue, $value);
147
        }
148
149
        foreach ($this->aliases as $name => $targetOption) {
150
            if ($option !== $targetOption) {
151
                $value = $this->injectOption($name, $targetOption, $value);
152
            }
153
        }
154
155
        return $value;
156
    }
157
158
    /**
159
     * @param string $name
160
     * @param int    $option
161
     * @param string $target
162
     * @return string
163
     */
164
    private function injectOption(string $name, int $option, string $target): string
165
    {
166
        if (strpos($target, "{{$name}}") === false) {
167
            return $target;
168
        }
169
170
        return str_replace("{{$name}}", $this->get($option), $target);
171
    }
172
173
    /**
174
     * @param string $name
175
     * @param string $value
176
     * @param string $target
177
     * @return string
178
     */
179
    private function injectValue(string $name, string $value, string $target): string
180
    {
181
        return str_replace("{{$name}}", $value, $target);
182
    }
183
}