SettingsManager   D
last analyzed

Complexity

Total Complexity 60

Size/Duplication

Total Lines 571
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Importance

Changes 0
Metric Value
wmc 60
c 0
b 0
f 0
lcom 1
cbo 15
dl 0
loc 571
rs 4.2857

28 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
A getCache() 0 4 1
A setCache() 0 4 1
A getActiveProfilesCookie() 0 4 1
A setActiveProfilesCookie() 0 4 1
A getActiveExperimentProfilesCookie() 0 4 1
A setActiveExperimentProfilesCookie() 0 4 1
A getActiveProfilesSettingName() 0 4 1
A setActiveProfilesSettingName() 0 4 1
A getActiveProfilesList() 0 4 1
A setActiveProfilesList() 0 4 1
A appendActiveProfilesList() 0 4 1
A getActiveExperimentsSettingName() 0 4 1
A setActiveExperimentsSettingName() 0 4 1
B create() 0 36 6
A delete() 0 21 3
A get() 0 11 1
A has() 0 11 2
A getValue() 0 10 2
B getCachedValue() 0 25 5
B getAllProfiles() 0 36 4
A getProfileSettings() 0 11 1
B getActiveProfiles() 0 23 4
A getAllExperiments() 0 6 1
A getActiveExperiments() 0 23 3
B toggleExperiment() 0 24 4
B getCachedExperiment() 0 20 5
B update() 0 27 5

How to fix   Complexity   

Complex Class

Complex classes like SettingsManager often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SettingsManager, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the ONGR package.
5
 *
6
 * (c) NFQ Technologies UAB <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace ONGR\SettingsBundle\Service;
13
14
use Doctrine\Common\Cache\CacheProvider;
15
use ONGR\CookiesBundle\Cookie\Model\GenericCookie;
16
use ONGR\ElasticsearchBundle\Result\Aggregation\AggregationValue;
17
use ONGR\ElasticsearchBundle\Result\DocumentIterator;
18
use ONGR\ElasticsearchDSL\Aggregation\Bucketing\FilterAggregation;
19
use ONGR\ElasticsearchDSL\Aggregation\Bucketing\TermsAggregation;
20
use ONGR\ElasticsearchDSL\Aggregation\Metric\TopHitsAggregation;
21
use ONGR\ElasticsearchDSL\Query\Compound\BoolQuery;
22
use ONGR\ElasticsearchDSL\Query\TermLevel\TermQuery;
23
use ONGR\SettingsBundle\Event\Events;
24
use ONGR\SettingsBundle\Event\SettingActionEvent;
25
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
26
use ONGR\ElasticsearchBundle\Service\Repository;
27
use ONGR\ElasticsearchBundle\Service\Manager;
28
use ONGR\SettingsBundle\Document\Setting;
29
use Symfony\Component\Serializer\Exception\LogicException;
30
31
/**
32
 * Class SettingsManager responsible for managing settings actions.
33
 */
34
class SettingsManager
35
{
36
    /**
37
     * Symfony event dispatcher.
38
     *
39
     * @var EventDispatcherInterface
40
     */
41
    private $eventDispatcher;
42
43
    /**
44
     * Elasticsearch manager which handles setting repository.
45
     *
46
     * @var Manager
47
     */
48
    private $manager;
49
50
    /**
51
     * Settings repository.
52
     *
53
     * @var Repository
54
     */
55
    private $repo;
56
57
    /**
58
     * Cache pool container.
59
     *
60
     * @var CacheProvider
61
     */
62
    private $cache;
63
64
    /**
65
     * Cookie storage for active cookies.
66
     *
67
     * @var GenericCookie
68
     */
69
    private $activeProfilesCookie;
70
71
    /**
72
     * Cookie storage for active cookies.
73
     *
74
     * @var GenericCookie
75
     */
76
    private $activeExperimentProfilesCookie;
77
78
    /**
79
     * Active profiles setting name to store in the cache engine.
80
     *
81
     * @var string
82
     */
83
    private $activeProfilesSettingName;
84
85
    /**
86
     * Active profiles list collected from es, cache and cookie.
87
     *
88
     * @var array
89
     */
90
    private $activeProfilesList = [];
91
92
    /**
93
     * Active experiments setting name to store in the cache engine.
94
     *
95
     * @var string
96
     */
97
    private $activeExperimentsSettingName;
98
99
    /**
100
     * @param Repository               $repo
101
     * @param EventDispatcherInterface $eventDispatcher
102
     */
103
    public function __construct(
104
        $repo,
105
        EventDispatcherInterface $eventDispatcher
106
    ) {
107
        $this->repo = $repo;
108
        $this->manager = $repo->getManager();
109
        $this->eventDispatcher = $eventDispatcher;
110
    }
111
112
    /**
113
     * @return CacheProvider
114
     */
115
    public function getCache()
116
    {
117
        return $this->cache;
118
    }
119
120
    /**
121
     * @param CacheProvider $cache
122
     */
123
    public function setCache($cache)
124
    {
125
        $this->cache = $cache;
126
    }
127
128
    /**
129
     * @return GenericCookie
130
     */
131
    public function getActiveProfilesCookie()
132
    {
133
        return $this->activeProfilesCookie;
134
    }
135
136
    /**
137
     * @param GenericCookie $activeProfilesCookie
138
     */
139
    public function setActiveProfilesCookie($activeProfilesCookie)
140
    {
141
        $this->activeProfilesCookie = $activeProfilesCookie;
142
    }
143
144
    /**
145
     * @return GenericCookie
146
     */
147
    public function getActiveExperimentProfilesCookie()
148
    {
149
        return $this->activeExperimentProfilesCookie;
150
    }
151
152
    /**
153
     * @param GenericCookie $activeExperimentProfilesCookie
154
     */
155
    public function setActiveExperimentProfilesCookie($activeExperimentProfilesCookie)
156
    {
157
        $this->activeExperimentProfilesCookie = $activeExperimentProfilesCookie;
158
    }
159
160
    /**
161
     * @return string
162
     */
163
    public function getActiveProfilesSettingName()
164
    {
165
        return $this->activeProfilesSettingName;
166
    }
167
168
    /**
169
     * @param string $activeProfilesSettingName
170
     */
171
    public function setActiveProfilesSettingName($activeProfilesSettingName)
172
    {
173
        $this->activeProfilesSettingName = $activeProfilesSettingName;
174
    }
175
176
    /**
177
     * @return array
178
     */
179
    public function getActiveProfilesList()
180
    {
181
        return $this->activeProfilesList;
182
    }
183
184
    /**
185
     * @param array $activeProfilesList
186
     */
187
    public function setActiveProfilesList(array $activeProfilesList)
188
    {
189
        $this->activeProfilesList = $activeProfilesList;
190
    }
191
192
    /**
193
     * @param array $activeProfilesList
194
     */
195
    public function appendActiveProfilesList(array $activeProfilesList)
196
    {
197
        $this->activeProfilesList = array_merge($this->activeProfilesList, $activeProfilesList);
198
    }
199
200
    /**
201
     * @return string
202
     */
203
    public function getActiveExperimentsSettingName()
204
    {
205
        return $this->activeExperimentsSettingName;
206
    }
207
208
    /**
209
     * @param string $activeExperimentsSettingName
210
     */
211
    public function setActiveExperimentsSettingName($activeExperimentsSettingName)
212
    {
213
        $this->activeExperimentsSettingName = $activeExperimentsSettingName;
214
    }
215
216
    /**
217
     * Creates setting.
218
     *
219
     * @param array        $data
220
     *
221
     * @return Setting
222
     */
223
    public function create(array $data = [])
224
    {
225
        $data = array_filter($data);
226
        if (!isset($data['name']) || !isset($data['type'])) {
227
            throw new \LogicException('Missing one of the mandatory field!');
228
        }
229
230
        if (!isset($data['value'])) {
231
            $data['value'] = 0;
232
        }
233
234
        $name = $data['name'];
235
        $existingSetting = $this->get($name);
236
237
        if ($existingSetting) {
238
            throw new \LogicException(sprintf('Setting %s already exists.', $name));
239
        }
240
241
        $settingClass = $this->repo->getClassName();
242
        /** @var Setting $setting */
243
        $setting = new $settingClass();
244
245
        $this->eventDispatcher->dispatch(Events::PRE_CREATE, new SettingActionEvent($name, $data, $setting));
246
247
        #TODO Introduce array populate function in Setting document instead of this foreach.
248
        foreach ($data as $key => $value) {
249
            $setting->{'set'.ucfirst($key)}($value);
250
        }
251
252
        $this->manager->persist($setting);
253
        $this->manager->commit();
254
255
        $this->eventDispatcher->dispatch(Events::POST_CREATE, new SettingActionEvent($name, $data, $setting));
256
257
        return $setting;
258
    }
259
260
    /**
261
     * Overwrites setting parameters with given name.
262
     *
263
     * @param string      $name
264
     * @param array       $data
265
     *
266
     * @return Setting
267
     */
268
    public function update($name, $data = [])
269
    {
270
        $setting = $this->get($name);
271
272
        if (!$setting) {
273
            throw new \LogicException(sprintf('Setting %s not exist.', $name));
274
        }
275
276
        $this->eventDispatcher->dispatch(Events::PRE_UPDATE, new SettingActionEvent($name, $data, $setting));
277
278
        #TODO Add populate function to document class
279
        foreach ($data as $key => $value) {
280
            $setting->{'set'.ucfirst($key)}($value);
281
        }
282
283
        $this->manager->persist($setting);
284
        $this->manager->commit();
285
        $this->cache->delete($name);
286
287
        if ($setting->getType() == 'experiment' || $setting->getName() == 'ongr_active_experiments') {
288
            $this->getActiveExperimentProfilesCookie()->setClear(true);
289
        }
290
291
        $this->eventDispatcher->dispatch(Events::PRE_UPDATE, new SettingActionEvent($name, $data, $setting));
292
293
        return $setting;
294
    }
295
296
    /**
297
     * Deletes a setting.
298
     *
299
     * @param string    $name
300
     *
301
     * @throws \LogicException
302
     * @return array
303
     */
304
    public function delete($name)
305
    {
306
        if ($this->has($name)) {
307
            $this->eventDispatcher->dispatch(Events::PRE_UPDATE, new SettingActionEvent($name, [], null));
308
309
            $setting = $this->get($name);
310
            $this->cache->delete($name);
311
            $response = $this->repo->remove($setting->getId());
312
313
            if ($setting->getType() == 'experiment') {
314
                $this->cache->delete($this->activeExperimentsSettingName);
315
                $this->getActiveExperimentProfilesCookie()->setClear(true);
316
            }
317
318
            $this->eventDispatcher->dispatch(Events::PRE_UPDATE, new SettingActionEvent($name, $response, $setting));
0 ignored issues
show
Documentation introduced by
$response is of type callable, but the function expects a null|array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
319
320
            return $response;
321
        }
322
323
        throw new \LogicException(sprintf('Setting with name %s doesn\'t exist.', $name));
324
    }
325
326
    /**
327
     * Returns setting object.
328
     *
329
     * @param string $name
330
     *
331
     * @return Setting
332
     */
333
    public function get($name)
334
    {
335
        $this->eventDispatcher->dispatch(Events::PRE_GET, new SettingActionEvent($name, [], null));
336
337
        /** @var Setting $setting */
338
        $setting = $this->repo->findOneBy(['name.name' => $name]);
339
340
        $this->eventDispatcher->dispatch(Events::PRE_GET, new SettingActionEvent($name, [], $setting));
341
342
        return $setting;
343
    }
344
345
    /**
346
     * Returns setting object.
347
     *
348
     * @param string $name
349
     *
350
     * @return bool
351
     */
352
    public function has($name)
353
    {
354
        /** @var Setting $setting */
355
        $setting = $this->repo->findOneBy(['name.name' => $name]);
356
357
        if ($setting) {
358
            return true;
359
        }
360
361
        return false;
362
    }
363
364
    /**
365
     * Get setting value by current active profiles setting.
366
     *
367
     * @param string $name
368
     * @param bool $default
369
     *
370
     * @return string|array|bool
371
     */
372
    public function getValue($name, $default = null)
373
    {
374
        $setting = $this->get($name);
375
376
        if ($setting) {
377
            return $setting->getValue();
378
        }
379
380
        return $default;
381
    }
382
383
    /**
384
     * Get setting value by checking also from cache engine.
385
     *
386
     * @param string $name
387
     * @param bool   $checkWithActiveProfiles Checks if setting is in active profile.
388
     *
389
     * @return mixed
390
     */
391
    public function getCachedValue($name, $checkWithActiveProfiles = true)
392
    {
393
        if ($this->cache->contains($name)) {
394
            $setting = $this->cache->fetch($name);
395
        } elseif ($this->has($name)) {
396
            $settingDocument = $this->get($name);
397
            $setting = [
398
                'value' => $settingDocument->getValue(),
399
                'profiles' => $settingDocument->getProfile(),
400
            ];
401
            $this->cache->save($name, $setting);
402
        } else {
403
            return null;
404
        }
405
406
        if ($checkWithActiveProfiles) {
407
            if (count(array_intersect($this->getActiveProfiles(), $setting['profiles']))) {
408
                return $setting['value'];
409
            }
410
411
            return null;
412
        }
413
414
        return $setting['value'];
415
    }
416
417
    /**
418
     * Get all full profile information.
419
     *
420
     * @return array
421
     */
422
    public function getAllProfiles()
423
    {
424
        $profiles = [];
425
426
        $search = $this->repo->createSearch();
427
        $filter = new BoolQuery();
428
        $filter->add(new TermQuery('type', 'experiment'), BoolQuery::MUST_NOT);
429
        $topHitsAgg = new TopHitsAggregation('documents', 20);
430
        $termAgg = new TermsAggregation('profiles', 'profile.profile');
431
        $filterAgg = new FilterAggregation('filter', $filter);
432
        $termAgg->addAggregation($topHitsAgg);
433
        $filterAgg->addAggregation($termAgg);
434
        $search->addAggregation($filterAgg);
435
436
        $result = $this->repo->findDocuments($search);
437
438
        /** @var Setting $activeProfiles */
439
        $activeProfiles = $this->getValue($this->activeProfilesSettingName, []);
0 ignored issues
show
Documentation introduced by
array() is of type array, but the function expects a boolean|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
440
441
        /** @var AggregationValue $agg */
442
        foreach ($result->getAggregation('filter')->getAggregation('profiles') as $agg) {
443
            $settings = [];
444
            $docs = $agg->getAggregation('documents');
445
            foreach ($docs['hits']['hits'] as $doc) {
446
                $settings[] = $doc['_source']['name'];
447
            }
448
            $name = $agg->getValue('key');
449
            $profiles[] = [
450
                'active' => $activeProfiles ? in_array($agg->getValue('key'), (array)$activeProfiles) : false,
451
                'name' => $name,
452
                'settings' => implode(', ', $settings),
453
            ];
454
        }
455
456
        return $profiles;
457
    }
458
459
    /**
460
     * Returns profiles settings array
461
     *
462
     * @param string $profile
463
     *
464
     * @return array
465
     */
466
    public function getProfileSettings($profile)
467
    {
468
        $search = $this->repo->createSearch();
469
        $termQuery = new TermQuery('profile', $profile);
470
        $search->addQuery($termQuery);
471
        $search->setSize(1000);
472
473
        $settings = $this->repo->findArray($search);
474
475
        return $settings;
476
    }
477
478
    /**
479
     * Returns cached active profiles names list.
480
     *
481
     * @return array
482
     */
483
    public function getActiveProfiles()
484
    {
485
        if ($this->cache->contains($this->activeProfilesSettingName)) {
486
            $profiles = $this->cache->fetch($this->activeProfilesSettingName);
487
        } else {
488
            $profiles = [];
489
            $allProfiles = $this->getAllProfiles();
490
491
            foreach ($allProfiles as $profile) {
492
                if (!$profile['active']) {
493
                    continue;
494
                }
495
496
                $profiles[] = $profile['name'];
497
            }
498
499
            $this->cache->save($this->activeProfilesSettingName, $profiles);
500
        }
501
502
        $profiles = array_merge($profiles, $this->activeProfilesList);
503
504
        return $profiles;
505
    }
506
507
    /**
508
     * @return DocumentIterator
509
     */
510
    public function getAllExperiments()
511
    {
512
        $experiments = $this->repo->findBy(['type' => 'experiment']);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->repo->findBy(arra...ype' => 'experiment')); of type array|ONGR\Elasticsearch...Result\DocumentIterator adds the type array to the return on line 514 which is incompatible with the return type documented by ONGR\SettingsBundle\Serv...ager::getAllExperiments of type ONGR\ElasticsearchBundle\Result\DocumentIterator.
Loading history...
513
514
        return $experiments;
515
    }
516
517
    /**
518
     * Returns an array of active experiments names either from cache or from es.
519
     * If none are found, the setting with no active experiments is created.
520
     *
521
     * @return array
522
     */
523
    public function getActiveExperiments()
524
    {
525
        if ($this->cache->contains($this->activeExperimentsSettingName)) {
526
            return $this->cache->fetch($this->activeExperimentsSettingName)['value'];
527
        }
528
529
        if ($this->has($this->activeExperimentsSettingName)) {
530
            $experiments = $this->get($this->activeExperimentsSettingName)->getValue();
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->get($this->active...ttingName)->getValue(); of type string adds the type string to the return on line 544 which is incompatible with the return type documented by ONGR\SettingsBundle\Serv...r::getActiveExperiments of type array.
Loading history...
531
        } else {
532
            $this->create(
533
                [
534
                    'name' => $this->activeExperimentsSettingName,
535
                    'value' => [],
536
                    'type' => 'hidden',
537
                ]
538
            );
539
            $experiments = [];
540
        }
541
542
        $this->cache->save($this->activeExperimentsSettingName, ['value' => $experiments]);
543
544
        return $experiments;
545
    }
546
547
    /**
548
     * @param string $name
549
     */
550
    public function toggleExperiment($name)
551
    {
552
        if (!$this->has($this->activeExperimentsSettingName)) {
553
            throw new LogicException(
554
                sprintf('The setting `%s` is not set', $this->activeExperimentsSettingName)
555
            );
556
        }
557
558
        $setting = $this->get($this->activeExperimentsSettingName);
559
        $experiments = $setting->getValue();
560
561
        if (is_array($experiments)) {
562
            if (($key = array_search($name, $experiments)) !== false) {
563
                unset($experiments[$key]);
564
                $experiments = array_values($experiments);
565
            } else {
566
                $experiments[] = $name;
567
            }
568
        } else {
569
            $experiments = [$name];
570
        }
571
572
        $this->update($setting->getName(), ['value' => $experiments]);
573
    }
574
575
    /**
576
     * Get full experiment by caching.
577
     *
578
     * @param string $name
579
     *
580
     * @return array|null
581
     *
582
     * @throws LogicException
583
     */
584
    public function getCachedExperiment($name)
585
    {
586
        if ($this->cache->contains($name)) {
587
            $experiment = $this->cache->fetch($name);
588
        } elseif ($this->has($name)) {
589
            $experiment = $this->get($name)->getSerializableData();
590
        } else {
591
            return null;
592
        }
593
594
        if (!isset($experiment['type']) || $experiment['type'] !== 'experiment') {
595
            throw new LogicException(
596
                sprintf('The setting `%s` was found but it is not an experiment', $name)
597
            );
598
        }
599
600
        $this->cache->save($name, $experiment);
601
602
        return $experiment;
603
    }
604
}
605