InstanceSettingsProvider::isAmbiguous()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 6
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
1
<?php declare(strict_types=1);
2
3
namespace Limoncello\Application\Settings;
4
5
/**
6
 * Copyright 2015-2020 [email protected]
7
 *
8
 * Licensed under the Apache License, Version 2.0 (the "License");
9
 * you may not use this file except in compliance with the License.
10
 * You may obtain a copy of the License at
11
 *
12
 * http://www.apache.org/licenses/LICENSE-2.0
13
 *
14
 * Unless required by applicable law or agreed to in writing, software
15
 * distributed under the License is distributed on an "AS IS" BASIS,
16
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
 * See the License for the specific language governing permissions and
18
 * limitations under the License.
19
 */
20
21
use Limoncello\Application\Exceptions\AlreadyRegisteredSettingsException;
22
use Limoncello\Application\Exceptions\AmbiguousSettingsException;
23
use Limoncello\Application\Exceptions\NotRegisteredSettingsException;
24
use Limoncello\Contracts\Settings\SettingsInterface;
25
use Limoncello\Contracts\Settings\SettingsProviderInterface;
26
use function array_key_exists;
27
use function assert;
28
use function class_implements;
29
use function class_parents;
30
use function count;
31
use function get_class;
32
use function is_subclass_of;
33
34
/**
35
 * @package Limoncello\Application
36
 */
37
class InstanceSettingsProvider implements SettingsProviderInterface
38
{
39
    /**
40
     * @var array
41
     */
42
    private $applicationData;
43
44
    /**
45
     * @var SettingsInterface[]
46
     */
47
    private $instances = [];
48
49
    /**
50
     * @var bool
51
     */
52
    private $isProcessed = true;
53
54
    /**
55
     * @var array
56
     */
57
    private $settingsMap = [];
58
59
    /**
60
     * @var array
61
     */
62
    private $settingsData = [];
63 13
64
    /**
65 13
     * @var array
66
     */
67
    private $ambiguousMap = [];
68
69
    /**
70
     * @param array $applicationData
71 3
     */
72
    public function __construct(array $applicationData)
73 3
    {
74
        $this->applicationData = $applicationData;
75 3
    }
76
77 3
    /**
78
     * @inheritdoc
79
     */
80
    public function has(string $className): bool
81
    {
82
        $this->checkInstancesAreProcessed();
83
84
        $result = array_key_exists($className, $this->getSettingsMap());
85 3
86
        return $result;
87 3
    }
88 2
89 1
    /**
90
     * @param string $className
91 1
     *
92
     * @return array
93
     */
94 1
    public function get(string $className): array
95 1
    {
96
        if ($this->has($className) === false) {
97 1
            if (array_key_exists($className, $this->ambiguousMap) === true) {
98
                throw new AmbiguousSettingsException($className);
99
            }
100
            throw new NotRegisteredSettingsException($className);
101
        }
102
103
        $index = $this->settingsMap[$className];
104
        $data  = $this->settingsData[$index];
105 9
106
        return $data;
107 9
    }
108 9
109 1
    /**
110
     * @param SettingsInterface $settings
111
     *
112 9
     * @return InstanceSettingsProvider
113 9
     */
114
    public function register(SettingsInterface $settings): InstanceSettingsProvider
115 9
    {
116
        $className = get_class($settings);
117
        if (array_key_exists($className, $this->instances) === true) {
118
            throw new AlreadyRegisteredSettingsException($className);
119
        }
120
121 9
        $this->instances[$className] = $settings;
122
        $this->isProcessed           = false;
123 9
124
        return $this;
125 9
    }
126
127
    /**
128
     * @return array
129
     */
130
    public function getSettingsMap(): array
131 7
    {
132
        $this->checkInstancesAreProcessed();
133 7
134
        return $this->settingsMap;
135 7
    }
136
137
    /**
138
     * @return array
139
     */
140
    public function getSettingsData(): array
141 7
    {
142
        $this->checkInstancesAreProcessed();
143 7
144
        return $this->settingsData;
145 7
    }
146
147
    /**
148
     * @return array
149
     */
150
    public function getAmbiguousMap(): array
151 1
    {
152
        $this->checkInstancesAreProcessed();
153 1
154
        return $this->ambiguousMap;
155 1
    }
156
157
    /**
158
     * @inheritdoc
159
     */
160
    public function isAmbiguous(string $className): bool
161 8
    {
162
        $result = array_key_exists($className, $this->getAmbiguousMap());
163 8
164
        return $result;
165
    }
166
167
    /**
168
     * @return array
169 9
     */
170
    protected function getApplicationData(): array
171 9
    {
172
        return $this->applicationData;
173
    }
174
175
    /**
176
     * @return void
177
     */
178
    private function checkInstancesAreProcessed(): void
179 8
    {
180
        $this->isProcessed === true ?: $this->processInstances();
181 8
    }
182 8
183 8
    /**
184 8
     * @return void
185 7
     *
186
     * @SuppressWarnings(PHPMD.ElseExpression)
187 8
     */
188 8
    private function processInstances(): void
189
    {
190
        $preliminaryMap = [];
191
        foreach ($this->instances as $instance) {
192 8
            $preliminaryMap[get_class($instance)][] = $instance;
193 8
            foreach (class_parents($instance) as $parentClass) {
194 8
                $preliminaryMap[$parentClass][] = $instance;
195
            }
196 8
            foreach (class_implements($instance) as $parentClass) {
197 8
                $preliminaryMap[$parentClass][] = $instance;
198 8
            }
199
        }
200 8
201 8
        $nextIndex    = 0;
202 8
        $hashMap      = []; // hash  => index
203
        $settingsData = []; // index => instance data
204
        $getIndex     = function (SettingsInterface $instance) use (&$nextIndex, &$hashMap, &$settingsData): int {
205 8
            $hash = spl_object_hash($instance);
206 8
            if (array_key_exists($hash, $hashMap) === true) {
207
                $index = $hashMap[$hash];
208 8
            } else {
209 8
                $hashMap[$hash]           = $nextIndex;
210 8
                $settingsData[$nextIndex] = $instance->get($this->getApplicationData());
211 8
                $index                    = $nextIndex++;
212 8
            }
213
214 7
            return $index;
215
        };
216 8
217 8
        $settingsMap  = []; // class => index
218
        $ambiguousMap = []; // class => true
219 8
        foreach ($preliminaryMap as $class => $instanceList) {
220
            if (count($instanceList) === 1) {
221
                $selected = $instanceList[0];
222
            } else {
223 8
                $selected = $this->selectChildSettings($instanceList);
224 8
            }
225 8
            if ($selected !== null) {
226 8
                $settingsMap[$class] = $getIndex($selected);
227
            } else {
228
                $ambiguousMap[$class] = true;
229
            }
230
        }
231
232
        $this->settingsMap  = $settingsMap;
233
        $this->settingsData = $settingsData;
234 7
        $this->ambiguousMap = $ambiguousMap;
235
        $this->isProcessed  = true;
236 7
    }
237 7
238 7
    /**
239 7
     * @param SettingsInterface[] $instanceList
240 7
     *
241 7
     * @return SettingsInterface|null
242 7
     */
243 7
    private function selectChildSettings(array $instanceList): ?SettingsInterface
244
    {
245
        $count = count($instanceList);
246
        assert($count > 1);
247
        $selected = $this->selectChildSettingsAmongTwo($instanceList[0], $instanceList[1]);
248 7
        if ($selected !== null) {
249
            for ($index = 2; $index < $count; ++$index) {
250
                $selected = $this->selectChildSettingsAmongTwo($selected, $instanceList[$index]);
251
                if ($selected === null) {
252
                    break;
253
                }
254
            }
255
        }
256
257 7
        return $selected;
258
    }
259
260
    /**
261 7
     * @param SettingsInterface $instance1
262 7
     * @param SettingsInterface $instance2
263
     *
264
     * @return SettingsInterface|null
265
     */
266
    private function selectChildSettingsAmongTwo(
267
        SettingsInterface $instance1,
268
        SettingsInterface $instance2
269
    ): ?SettingsInterface {
270
        return is_subclass_of($instance1, get_class($instance2)) === true ?
271
            $instance1 : (is_subclass_of($instance2, get_class($instance1)) === true ? $instance2 : null);
272
    }
273
}
274