Completed
Push — master ( 177102...bcf206 )
by Rafał
08:48
created

SettingsManager::get()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 39
rs 8.6737
c 0
b 0
f 0
cc 6
nc 14
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Superdesk Web Publisher Settings Bundle.
7
 *
8
 * Copyright 2017 Sourcefabric z.ú. and contributors.
9
 *
10
 * For the full copyright and license information, please see the
11
 * AUTHORS and LICENSE files distributed with this source code.
12
 *
13
 * @copyright 2017 Sourcefabric z.ú
14
 * @license http://www.superdesk.org/license
15
 */
16
17
namespace SWP\Bundle\SettingsBundle\Manager;
18
19
use Doctrine\Common\Persistence\ObjectManager;
20
use Exception;
21
use SWP\Bundle\SettingsBundle\Context\ScopeContextInterface;
22
use SWP\Bundle\SettingsBundle\Exception\InvalidOwnerException;
23
use SWP\Bundle\SettingsBundle\Exception\InvalidScopeException;
24
use SWP\Bundle\SettingsBundle\Model\SettingsInterface;
25
use SWP\Bundle\SettingsBundle\Model\SettingsOwnerInterface;
26
use SWP\Bundle\SettingsBundle\Model\SettingsRepositoryInterface;
27
use SWP\Bundle\SettingsBundle\Provider\SettingsProviderInterface;
28
use SWP\Component\Storage\Factory\FactoryInterface;
29
30
class SettingsManager implements SettingsManagerInterface
31
{
32
    protected $settingsProvider;
33
34
    protected $em;
35
36
    protected $settingsRepository;
37
38
    protected $settingsFactory;
39
40
    protected $scopeContext;
41
42
    protected $internalCache = [];
43
44
    public function __construct(
45
        ObjectManager $em,
46
        SettingsProviderInterface $settingsProvider,
47
        SettingsRepositoryInterface $settingsRepository,
48
        FactoryInterface $settingsFactory,
49
        ScopeContextInterface $scopeContext
50
    ) {
51
        $this->em = $em;
52
        $this->settingsProvider = $settingsProvider;
53
        $this->settingsRepository = $settingsRepository;
54
        $this->settingsFactory = $settingsFactory;
55
        $this->scopeContext = $scopeContext;
56
    }
57
58
    public function get(string $name, $scope = ScopeContextInterface::SCOPE_GLOBAL, SettingsOwnerInterface $owner = null, $default = null)
59
    {
60
        $keyElements = [$name, $scope, $default, $this->scopeContext->getScopes()];
61
        if (null !== $owner) {
62
            $keyElements[] = $owner->getId();
63
        }
64
        $cacheKey = md5(json_encode($keyElements, JSON_THROW_ON_ERROR, 512));
65
        if (isset($this->internalCache[$cacheKey])) {
66
            return $this->internalCache[$cacheKey];
67
        }
68
69
        // Allow scope discovery from configuration
70
        if (null !== $scope) {
71
            $this->validateScopeAndOwner($scope, $owner);
72
        }
73
74
        $defaultSetting = $this->getFromConfiguration($scope, $name);
75
76
        /** @var SettingsInterface $setting */
77
        $setting = $this->getSettingFromRepository($name, $defaultSetting['scope'], $owner);
78
79
        if (null !== $setting) {
80
            $value = $this->decodeValue($defaultSetting['type'], $setting->getValue());
81
            $this->internalCache[$cacheKey] = $value;
82
83
            return $value;
84
        }
85
86
        if (null !== $default) {
87
            $this->internalCache[$cacheKey] = $default;
88
89
            return $default;
90
        }
91
92
        $value = $this->decodeValue($defaultSetting['type'], $defaultSetting['value']);
93
        $this->internalCache[$cacheKey] = $value;
94
95
        return $value;
96
    }
97
98
    public function all(): array
99
    {
100
        $cacheKey = md5(json_encode(['all', $this->scopeContext], JSON_THROW_ON_ERROR, 512));
101
        if (isset($this->internalCache[$cacheKey])) {
102
            return $this->internalCache[$cacheKey];
103
        }
104
105
        $settings = $this->getFromConfiguration();
106
        $settings = $this->processSettings($settings, $this->getSettingsFromRepository());
107
        $this->internalCache[$cacheKey] = $settings;
108
109
        return $settings;
110
    }
111
112
    public function getByScopeAndOwner(string $scope, SettingsOwnerInterface $settingsOwner): array
113
    {
114
        $settings = $this->getFromConfiguration($scope);
115
        $persistedSettings = $this->settingsRepository->findByScopeAndOwner($scope, $settingsOwner)->getQuery()->getResult();
116
117
        return $this->processSettings($settings, $persistedSettings);
118
    }
119
120
    public function set(string $name, $value, $scope = ScopeContextInterface::SCOPE_GLOBAL, SettingsOwnerInterface $owner = null)
121
    {
122
        $this->internalCache = [];
123
        $this->validateScopeAndOwner($scope, $owner);
124
        $defaultSetting = $this->getFromConfiguration($scope, $name);
125
126
        /** @var SettingsInterface $setting */
127
        $setting = $this->getSettingFromRepository($name, $scope, $owner);
128
        if (null === $setting) {
129
            /** @var SettingsInterface $setting */
130
            $setting = $this->settingsFactory->create();
131
            $setting->setName($name);
132
            $setting->setScope($scope);
133
134
            if (null !== $owner) {
135
                $setting->setOwner($owner->getId());
136
            }
137
            $this->settingsRepository->persist($setting);
138
        } else {
139
            $setting->setUpdatedAt(new \DateTime());
140
        }
141
142
        $setting->setValue($this->encodeValue($defaultSetting['type'], $value));
143
        $this->settingsRepository->flush();
144
145
        return $setting;
146
    }
147
148
    public function getOneSettingByName(string $name): ?array
149
    {
150
        foreach ($this->all() as $setting) {
151
            if ($setting['name'] === $name) {
152
                return $setting;
153
            }
154
        }
155
156
        return null;
157
    }
158
159
    public function clear(string $name, $scope = ScopeContextInterface::SCOPE_GLOBAL, SettingsOwnerInterface $owner = null): bool
160
    {
161
        $this->internalCache = [];
162
        $this->validateScopeAndOwner($scope, $owner);
163
164
        $setting = $this->getSettingFromRepository($name, $scope, $owner);
165
        if (null !== $setting) {
166
            $this->settingsRepository->remove($setting);
167
168
            return true;
169
        }
170
171
        return false;
172
    }
173
174
    public function clearAllByScope(string $scope = ScopeContextInterface::SCOPE_GLOBAL): void
175
    {
176
        $this->validateScope($scope);
177
178
        $this->settingsRepository->removeAllByScope($scope);
179
    }
180
181
    protected function validateScope(string $scope): void
182
    {
183
        if (!\in_array($scope, $this->scopeContext->getScopes(), true)) {
184
            throw new InvalidScopeException($scope);
185
        }
186
    }
187
188
    protected function validateScopeAndOwner(string $scope, $owner = null)
189
    {
190
        $this->validateScope($scope);
191
192
        if (ScopeContextInterface::SCOPE_GLOBAL !== $scope && null === $owner) {
193
            throw new InvalidOwnerException($scope);
194
        }
195
    }
196
197
    private function processSettings(array $settings = [], array $persistedSettings = []): array
198
    {
199
        $convertedSettings = [];
200
201
        foreach ($settings as $key => $setting) {
202
            $setting['name'] = $key;
203
            $convertedSettings[] = $setting;
204
        }
205
206
        foreach ($persistedSettings as $key => $setting) {
207
            foreach ($convertedSettings as $keyConverted => $convertedSetting) {
208
                if (isset($convertedSetting['name']) && $convertedSetting['name'] === $setting->getName()) {
209
                    $convertedSetting['value'] = $this->decodeValue(
210
                        $convertedSetting['type'],
211
                        $setting->getValue()
212
                    );
213
214
                    $convertedSettings[$keyConverted] = $convertedSetting;
215
                }
216
            }
217
        }
218
219
        return $convertedSettings;
220
    }
221
222
    private function getFromConfiguration(string $scope = null, $name = null)
223
    {
224
        $settings = [];
225
        $settingsConfig = $this->settingsProvider->getSettings();
226
227
        if (null !== $name && array_key_exists($name, $settingsConfig)) {
228
            $setting = $settingsConfig[$name];
229
            if (null === $scope || $setting['scope'] === $scope) {
230
                return $settings[$name] = $setting;
231
            }
232
233
            throw new InvalidScopeException($scope);
234
        }
235
236
        if (null !== $name) {
237
            throw new Exception('There is no setting with this name.');
238
        }
239
240
        foreach ($settingsConfig as $key => $setting) {
241
            if (null === $scope || $setting['scope'] === $scope) {
242
                $setting['value'] = $this->decodeValue($setting['type'], $setting['value']);
243
                $settings[$key] = $setting;
244
            }
245
        }
246
247
        return $settings;
248
    }
249
250
    /**
251
     * @return array|mixed
252
     */
253
    private function getSettingsFromRepository()
254
    {
255
        return $this->settingsRepository->findAllByScopeAndOwner($this->scopeContext)->getQuery()->getResult();
256
    }
257
258
    private function getSettingFromRepository(string $name, string $scope, SettingsOwnerInterface $owner = null): ?SettingsInterface
259
    {
260
        return $this->settingsRepository
261
            ->findOneByNameAndScopeAndOwner($name, $scope, $owner)
262
            ->getQuery()
263
            ->getOneOrNullResult();
264
    }
265
266
    private function encodeValue(string $settingType, $value)
267
    {
268
        if ('string' === $settingType) {
269
            return (string) $value;
270
        }
271
272
        if (($actualType = gettype($value)) !== $settingType) {
273
            throw new Exception(sprintf('Value type should be "%s" not "%s"', $settingType, $actualType));
274
        }
275
276
        if ('array' === $settingType) {
277
            return json_encode($value);
278
        }
279
280
        return $value;
281
    }
282
283
    private function decodeValue(string $settingType, $value)
284
    {
285
        if ('array' === $settingType) {
286
            return json_decode($value, true);
287
        }
288
289
        if ('boolean' === $settingType) {
290
            return filter_var($value, FILTER_VALIDATE_BOOLEAN);
291
        }
292
293
        if ('integer' === $settingType) {
294
            return filter_var($value, FILTER_VALIDATE_INT);
295
        }
296
297
        return $value;
298
    }
299
}
300