• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

optimizely / php-sdk / 4377654340

pending completion
4377654340

Pull #260

github

GitHub
Merge b67fdafbc into fa9a3bf88
Pull Request #260: [FSSDK-8972] Run test cases in PHP v8.2

2881 of 2967 relevant lines covered (97.1%)

63.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.18
/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php
1
<?php
2
/**
3
 * Copyright 2020-2021, Optimizely Inc and Contributors
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 */
17
namespace Optimizely\OptimizelyConfig;
18

19
use Optimizely\Config\ProjectConfigInterface;
20
use Optimizely\Entity\Experiment;
21
use Optimizely\Entity\Variation;
22

23
class OptimizelyConfigService
24
{
25
    /**
26
     * @var array List of experiments in config.
27
     */
28
    private $experiments;
29

30
    /**
31
     * @var array List of feature flags in config.
32
     */
33
    private $featureFlags;
34

35
    /**
36
     * @var string Revision of config.
37
     */
38
    private $revision;
39

40
    /**
41
     * @var string environmentKey of the config.
42
     */
43
    private $environmentKey;
44

45
    /**
46
     * @var string sdkKey of the config.
47
     */
48
    private $sdkKey;
49

50
    /**
51
     * @var string String denoting datafile.
52
     */
53
    private $datafile;
54

55
    /**
56
     * Map of experiment IDs to FeatureFlags.
57
     *
58
     * @var <string, FeatureFlag> associative array.
59
     */
60
    private $experimentIdFeatureMap;
61

62
    /**
63
     * Map of feature keys to Map of variable keys to OptimizelyVariables map.
64
     *
65
     * @var <string, <string, OptimizelyVariable>> associative array.
66
     */
67
    private $featKeyOptlyVariableKeyVariableMap;
68

69
    /**
70
     * Map of feature keys to Map of variable IDs to OptimizelyVariables map.
71
     *
72
     * @var <string, <string, OptimizelyVariable>> associative array.
73
     */
74
    private $featKeyOptlyVariableIdVariableMap;
75

76
    public function __construct(ProjectConfigInterface $projectConfig)
77
    {
78
        $this->experiments = $projectConfig->getAllExperiments();
16✔
79
        $this->featureFlags = $projectConfig->getFeatureFlags();
16✔
80
        $this->revision = $projectConfig->getRevision();
16✔
81
        $this->datafile = $projectConfig->toDatafile();
16✔
82
        $this->environmentKey = $projectConfig->getEnvironmentKey();
16✔
83
        $this->sdkKey = $projectConfig->getSdkKey();
16✔
84
        $this->projectConfig = $projectConfig;
16✔
85
        
86
        $this->createLookupMaps();
16✔
87
    }
16✔
88

89
    /**
90
     * @return OptimizelyConfig Instance of OptimizelyConfig for the given project config.
91
     */
92
    public function getConfig()
93
    {
94
        $experimentsMaps = $this->getExperimentsMaps();
6✔
95
        $featuresMap = $this->getFeaturesMap($experimentsMaps[1]);
6✔
96
        $attributes = $this->getConfigAttributes();
6✔
97
        $audiences = $this->getConfigAudiences();
6✔
98
        $events = $this->getConfigEvents();
6✔
99

100
        return new OptimizelyConfig(
6✔
101
            $this->revision,
6✔
102
            # This experimentsMap is for experiments of legacy projects only.
103
            # For flag projects, experiment keys are not guaranteed to be unique
104
            # across multiple flags, so this map may not include all experiments
105
            # when keys conflict. Use experimentRules and deliveryRules instead.
106
            $experimentsMaps[0],
6✔
107
            $featuresMap,
6✔
108
            $this->datafile,
6✔
109
            $this->environmentKey,
6✔
110
            $this->sdkKey,
6✔
111
            $attributes,
6✔
112
            $audiences,
6✔
113
            $events
114
        );
6✔
115
    }
116

117
    /**
118
     * Generates array of attributes as OptimizelyAttribute.
119
     *
120
     * @return array of OptimizelyAttributes.
121
     */
122
    protected function getConfigAttributes()
123
    {
124
        $attributeArray = [];
7✔
125
        $attributes = $this->projectConfig->getAttributes();
7✔
126
        foreach ($attributes as $attr) {
7✔
127
            $optlyAttr = new OptimizelyAttribute(
6✔
128
                $attr['id'],
6✔
129
                $attr['key']
6✔
130
            );
6✔
131
            array_push($attributeArray, $optlyAttr);
6✔
132
        }
7✔
133
        return $attributeArray;
7✔
134
    }
135

136

137
    /**
138
     * Generates array of events as OptimizelyEvents.
139
     *
140
     * @return array of OptimizelyEvents.
141
     */
142
    protected function getConfigEvents()
143
    {
144
        $eventsArray = [];
7✔
145
        $events = $this->projectConfig->getEvents();
7✔
146
        foreach ($events as $event) {
7✔
147
            $optlyEvent = new OptimizelyEvent(
5✔
148
                $event['id'],
5✔
149
                $event['key'],
5✔
150
                $event['experimentIds']
5✔
151
            );
5✔
152
            $eventsArray[] = $optlyEvent;
5✔
153
        }
7✔
154
        return $eventsArray;
7✔
155
    }
156

157
    /**
158
     * Generates array of audiences giving typed audiences high priority as OptimizelyAudience.
159
     *
160
     * @return array of OptimizelyEvents.
161
     */
162
    protected function getConfigAudiences()
163
    {
164
        $allAudiences = [];
7✔
165
        $typedAudienceIds = [];
7✔
166
        $audiences = $this->projectConfig->getAudiences();
7✔
167
        $typedAudiences = $this->projectConfig->getTypedAudiences();
7✔
168
        $audiencesArray = $typedAudiences;
7✔
169
        foreach ($audiencesArray as $key => $typedAudience) {
7✔
170
            $typedAudienceIds[] = $typedAudience['id'];
2✔
171
            $id = $typedAudience['id'];
2✔
172
            if ($id != '$opt_dummy_audience') {
2✔
173
                $optlyAudience = new OptimizelyAudience(
2✔
174
                    $id,
2✔
175
                    $typedAudience['name'],
2✔
176
                    json_encode($typedAudience['conditions'])
2✔
177
                );
2✔
178
                array_push($allAudiences, $optlyAudience);
2✔
179
            }
2✔
180
        }
7✔
181
        foreach ($audiences as $naudience) {
7✔
182
            if (!in_array($naudience['id'], $typedAudienceIds, true)) {
7✔
183
                $id = $naudience['id'];
7✔
184
                if ($id != '$opt_dummy_audience') {
7✔
185
                    $optlyAudience = new OptimizelyAudience(
5✔
186
                        $id,
5✔
187
                        $naudience['name'],
5✔
188
                        $naudience['conditions']
5✔
189
                    );
5✔
190
                    array_push($allAudiences, $optlyAudience);
5✔
191
                }
5✔
192
            }
7✔
193
        }
7✔
194
        return $allAudiences;
7✔
195
    }
196

197
    /**
198
     * Generates lookup maps to avoid redundant iteration while creating OptimizelyConfig.
199
     */
200
    protected function createLookupMaps()
201
    {
202
        $this->experimentIdFeatureMap = [];
16✔
203
        $this->featKeyOptlyVariableKeyVariableMap = [];
16✔
204
        $this->featKeyOptlyVariableIdVariableMap = [];
16✔
205

206
        foreach ($this->featureFlags as $feature) {
16✔
207
            # Populate experimentIdFeatureMap
208
            foreach ($feature->getExperimentIds() as $expId) {
16✔
209
                $this->experimentIdFeatureMap[$expId] = $feature;
16✔
210
            }
16✔
211
            $rolloutID = $feature->getRolloutId();
16✔
212
            $rollout = $this->projectConfig->getRolloutFromId($rolloutID);
16✔
213
            foreach ($rollout->getExperiments() as $exp) {
16✔
214
                $this->experimentIdFeatureMap[$exp->getId()] = $feature;
16✔
215
            }
16✔
216
            # Populate featKeyOptlyVariableKeyVariableMap and featKeyOptlyVariableIdVariableMap
217
            $variablesKeyMap = [];
16✔
218
            $variablesIdMap = [];
16✔
219

220
            foreach ($feature->getVariables() as $variable) {
16✔
221
                $variableId = $variable->getId();
16✔
222
                $variableKey = $variable->getKey();
16✔
223

224
                $optVariable = new OptimizelyVariable(
16✔
225
                    $variableId,
16✔
226
                    $variableKey,
16✔
227
                    $variable->getType(),
16✔
228
                    $variable->getDefaultValue()
16✔
229
                );
16✔
230

231
                $variablesKeyMap[$variableKey] = $optVariable;
16✔
232
                $variablesIdMap[$variableId] = $optVariable;
16✔
233
            }
16✔
234

235
            $featureKey = $feature->getKey();
16✔
236
            $this->featKeyOptlyVariableKeyVariableMap[$featureKey] = $variablesKeyMap;
16✔
237
            $this->featKeyOptlyVariableIdVariableMap[$featureKey] = $variablesIdMap;
16✔
238
        }
16✔
239
    }
16✔
240

241
    /**
242
     * Generates Variables map for the given Experiment and Variation.
243
     *
244
     * @param Experiment
245
     * @param Variation
246
     *
247
     * @return <String, OptimizelyVariable> Map of Variable key to OptimizelyVariable.
248
     */
249
    protected function getVariablesMap(Experiment $experiment, Variation $variation)
250
    {
251
        $experimentId = $experiment->getId();
12✔
252
        if (!array_key_exists($experimentId, $this->experimentIdFeatureMap)) {
12✔
253
            return [];
6✔
254
        }
255

256
        $featureFlag = $this->experimentIdFeatureMap[$experimentId];
11✔
257
        $featureKey = $featureFlag->getKey();
11✔
258

259
        // Set default variables for variation.
260
        $variablesMap = $this->featKeyOptlyVariableKeyVariableMap[$featureKey];
11✔
261
        
262
        // Return default variable values if feature is not enabled.
263
        if (!$variation->getFeatureEnabled()) {
11✔
264
            return $variablesMap;
8✔
265
        }
266

267
        // Set variation specific value if any.
268
        foreach ($variation->getVariables() as $variableUsage) {
10✔
269
            $id = $variableUsage->getId();
8✔
270
    
271
            $optVariable = $this->featKeyOptlyVariableIdVariableMap[$featureKey][$id];
8✔
272
    
273
            $key = $optVariable->getKey();
8✔
274
            $value = $variableUsage->getValue();
8✔
275
            $type = $optVariable->getType();
8✔
276
            
277
            $modifiedOptVariable = new OptimizelyVariable(
8✔
278
                $id,
8✔
279
                $key,
8✔
280
                $type,
8✔
281
                $value
282
            );
8✔
283

284
            $variablesMap[$key] = $modifiedOptVariable;
8✔
285
        }
10✔
286

287
        return $variablesMap;
10✔
288
    }
289

290
    
291
    /**
292
     * Generates Variations map for the given Experiment.
293
     *
294
     * @param Experiment
295
     *
296
     * @return <String, OptimizelyVariation> Map of Variation key to OptimizelyVariation.
297
     */
298
    protected function getVariationsMap(Experiment $experiment)
299
    {
300
        $variationsMap = [];
9✔
301

302
        foreach ($experiment->getVariations() as $variation) {
9✔
303
            $variablesMap = $this->getVariablesMap($experiment, $variation);
9✔
304
 
305
            $variationKey = $variation->getKey();
9✔
306
            $optVariation = new OptimizelyVariation(
9✔
307
                $variation->getId(),
9✔
308
                $variationKey,
9✔
309
                $variation->getFeatureEnabled(),
9✔
310
                $variablesMap
311
            );
9✔
312

313
            $variationsMap[$variationKey] = $optVariation;
9✔
314
        }
9✔
315

316
        return $variationsMap;
9✔
317
    }
318

319
    /**
320
     * Converts array of audience conditions to serialized audiences.
321
     *
322
     * for examples:
323
     * 1. Input: ["or", "1", "2"]
324
     *     Output: "us" OR "female"
325
     * 2. Input: ["not", "1"]
326
     *     Output: "NOT "us"
327
     * 3. Input: ["or", "1"]
328
     *     Output: "us"
329
     * 4. Input: ["and", ["or", "1", ["and", "2", "3"]], ["and", "11", ["or", "12", "13"]]]
330
     *     Output: "("us" OR ("female" AND "adult")) AND ("fr" AND ("male" OR "kid"))"
331
     *
332
     * @param array audience conditions .
333
     *
334
     * @return string of experiment audience conditions.
335
     */
336
    protected function getSerializedAudiences(array $audienceConditions)
337
    {
338
        $operators = ['and', 'or', 'not'];
3✔
339
        $finalAudiences = '';
3✔
340
        if ($audienceConditions == null) {
3✔
341
            return $finalAudiences;
1✔
342
        }
343
        $cond = '';
3✔
344
        foreach ($audienceConditions as $var) {
3✔
345
            $subAudience = '';
3✔
346
            // Checks if item is list of conditions means if it is sub audience
347
            if (is_array($var)) {
3✔
348
                $subAudience = '(' . $this->getSerializedAudiences($var) . ')';
2✔
349
            } elseif (in_array($var, $operators, true)) {
3✔
350
                $cond = strtoupper(strval($var));
3✔
351
            } else {
3✔
352
                // Checks if item is audience id
353
                $itemStr = strval($var);
3✔
354
                $audience = $this->projectConfig->getAudience($itemStr);
3✔
355
                $name = $audience == null ? $itemStr : $audience->getName();
3✔
356
                // if audience condition is "NOT" then add "NOT" at start. Otherwise check if there is already audience id in finalAudiences then append condition between finalAudiences and item
357
                if ($finalAudiences !== '' || $cond == "NOT") {
3✔
358
                    if ($finalAudiences !== '') {
2✔
359
                        $finalAudiences .= ' ';
2✔
360
                    }
2✔
361
                    if ($cond == '') {
2✔
362
                        $cond = 'OR';
1✔
363
                    }
1✔
364
                    $finalAudiences .= $cond . ' ' . '"' . $name . '"';
2✔
365
                } else {
2✔
366
                    $finalAudiences = '"' . $name . '"';
3✔
367
                }
368
            }
369
            // Checks if sub audience is empty or not
370
            if ($subAudience !== '') {
3✔
371
                if ($finalAudiences !== '' || $cond == "NOT") {
2✔
372
                    if ($finalAudiences !== '') {
2✔
373
                        $finalAudiences .= ' ';
2✔
374
                    }
2✔
375
                    if ($cond == '') {
2✔
376
                        $cond = 'OR';
×
377
                    }
×
378
                    $finalAudiences = $finalAudiences . $cond . ' ' . $subAudience;
2✔
379
                } else {
2✔
380
                    $finalAudiences = $finalAudiences . $subAudience;
2✔
381
                }
382
            }
2✔
383
        }
3✔
384
        return $finalAudiences;
3✔
385
    }
386

387

388
    /**
389
     * Generates OptimizelyExperiment Key and ID Maps.
390
     * Returns an array with
391
     * [0] OptimizelyExperimentKeyMap Used to form OptimizelyConfig
392
     * [1] OptimizelyExperimentIdMap Used for quick lookup when forming Features for OptimizelyConfig.
393
     *
394
     * @return [<string, OptimizelyExperiment>, <string, OptimizelyExperiment>]
395
     */
396
    protected function getExperimentsMaps()
397
    {
398
        $experimentsKeyMap = [];
7✔
399
        $experimentsIdMap = [];
7✔
400

401
        foreach ($this->experiments as $exp) {
7✔
402
            $expId = $exp->getId();
6✔
403
            $expKey = $exp->getKey();
6✔
404
            $audiences = '';
6✔
405
            if ($exp->getAudienceConditions() != null) {
6✔
406
                $audienceConditions = $exp->getAudienceConditions();
2✔
407
                $audiences = $this->getSerializedAudiences($audienceConditions);
2✔
408
            }
2✔
409
            $optExp = new OptimizelyExperiment(
6✔
410
                $expId,
6✔
411
                $expKey,
6✔
412
                $this->getVariationsMap($exp),
6✔
413
                $audiences
414
            );
6✔
415

416
            $experimentsKeyMap[$expKey] = $optExp;
6✔
417
            $experimentsIdMap[$expId] = $optExp;
6✔
418
        }
7✔
419

420
        return [$experimentsKeyMap, $experimentsIdMap];
7✔
421
    }
422

423
    /**
424
     * Generates array of delivery rules for optimizelyFeature.
425
     *
426
     * @param string feature rollout id.
427
     *
428
     * @return array of optimizelyExperiments as delivery rules.
429
     */
430
    protected function getDeliveryRules($rollout_id)
431
    {
432
        $deliveryRules = [];
6✔
433
        $rollout = $this->projectConfig->getRolloutFromId($rollout_id);
6✔
434
        $experiments = $rollout->getExperiments();
6✔
435
        foreach ($experiments as $exp) {
6✔
436
            $expId = $exp->getId();
6✔
437
            $expKey = $exp->getKey();
6✔
438
            $audiences = '';
6✔
439
            if ($exp->getAudienceConditions() != null) {
6✔
440
                $audienceConditions = $exp->getAudienceConditions();
1✔
441
                $audiences = $this->getSerializedAudiences($audienceConditions);
1✔
442
            }
1✔
443
            $optExp = new OptimizelyExperiment(
6✔
444
                $expId,
6✔
445
                $expKey,
6✔
446
                $this->getVariationsMap($exp),
6✔
447
                $audiences
448
            );
6✔
449
            array_push($deliveryRules, $optExp);
6✔
450
        }
6✔
451

452
        return $deliveryRules;
6✔
453
    }
454

455

456
    /**
457
     * Generates Features map for the project config.
458
     *
459
     * @param array Map of ID to OptimizelyExperiments.
460
     *
461
     * @return <String, OptimizelyFeature> Map of Feature key to OptimizelyFeature.
462
     */
463
    protected function getFeaturesMap(array $experimentsIdMap)
464
    {
465
        $featuresMap = [];
7✔
466

467
        foreach ($this->featureFlags as $feature) {
7✔
468
            $featureKey = $feature->getKey();
7✔
469
            $experimentsMap = [];
7✔
470
            $experimentRules = [];
7✔
471
            $deliveryRules = [];
7✔
472
            if ($feature->getRolloutId() != null) {
7✔
473
                $deliveryRules = $this->getDeliveryRules($feature->getRolloutId());
6✔
474
            }
6✔
475
            foreach ($feature->getExperimentIds() as $expId) {
7✔
476
                $optExp = $experimentsIdMap[$expId];
6✔
477
                $experimentsMap[$optExp->getKey()] = $optExp;
6✔
478
                array_push($experimentRules, $optExp);
6✔
479
            }
7✔
480

481
            $variablesMap = $this->featKeyOptlyVariableKeyVariableMap[$featureKey];
7✔
482

483
            $optFeature = new OptimizelyFeature(
7✔
484
                $feature->getId(),
7✔
485
                $featureKey,
7✔
486
                # This experimentsMap is deprecated. Use experimentRules and deliveryRules instead.
487
                $experimentsMap,
7✔
488
                $variablesMap,
7✔
489
                $experimentRules,
7✔
490
                $deliveryRules
491
            );
7✔
492

493
            $featuresMap[$featureKey] = $optFeature;
7✔
494
        }
7✔
495

496
        return $featuresMap;
7✔
497
    }
498
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc