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

optimizely / php-sdk / 4536635044

pending completion
4536635044

Pull #265

github

GitHub
Merge 019774507 into 34bebf03a
Pull Request #265: [FSSDK-8940] Correct return type hints

2871 of 2957 relevant lines covered (97.09%)

63.82 hits per line

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

95.78
/src/Optimizely/Optimizely.php
1
<?php
2
/**
3
 * Copyright 2016-2022, Optimizely
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;
18

19
use Exception;
20
use Optimizely\Config\DatafileProjectConfig;
21
use Optimizely\Entity\Variation;
22
use Optimizely\Exceptions\InvalidAttributeException;
23
use Optimizely\Exceptions\InvalidEventTagException;
24
use Throwable;
25
use Monolog\Logger;
26
use Optimizely\Decide\OptimizelyDecideOption;
27
use Optimizely\Decide\OptimizelyDecision;
28
use Optimizely\Decide\OptimizelyDecisionMessage;
29
use Optimizely\DecisionService\DecisionService;
30
use Optimizely\DecisionService\FeatureDecision;
31
use Optimizely\OptimizelyDecisionContext;
32
use Optimizely\Entity\Experiment;
33
use Optimizely\Entity\FeatureVariable;
34
use Optimizely\Enums\DecisionNotificationTypes;
35
use Optimizely\ErrorHandler\ErrorHandlerInterface;
36
use Optimizely\ErrorHandler\NoOpErrorHandler;
37
use Optimizely\Event\Builder\EventBuilder;
38
use Optimizely\Event\Dispatcher\DefaultEventDispatcher;
39
use Optimizely\Event\Dispatcher\EventDispatcherInterface;
40
use Optimizely\Logger\LoggerInterface;
41
use Optimizely\Logger\NoOpLogger;
42
use Optimizely\Notification\NotificationCenter;
43
use Optimizely\Notification\NotificationType;
44
use Optimizely\OptimizelyConfig\OptimizelyConfigService;
45
use Optimizely\OptimizelyUserContext;
46
use Optimizely\ProjectConfigManager\HTTPProjectConfigManager;
47
use Optimizely\ProjectConfigManager\ProjectConfigManagerInterface;
48
use Optimizely\ProjectConfigManager\StaticProjectConfigManager;
49
use Optimizely\UserProfile\UserProfileServiceInterface;
50
use Optimizely\Utils\Errors;
51
use Optimizely\Utils\Validator;
52
use Optimizely\Utils\VariableTypeUtils;
53

54
/**
55
 * Class Optimizely
56
 *
57
 * @package Optimizely
58
 */
59
class Optimizely
60
{
61
    const EVENT_KEY = 'Event Key';
62
    const EXPERIMENT_KEY = 'Experiment Key';
63
    const FEATURE_FLAG_KEY = 'Feature Flag Key';
64
    const USER_ID = 'User ID';
65
    const VARIABLE_KEY = 'Variable Key';
66
    const VARIATION_KEY = 'Variation Key';
67

68
    /**
69
     * @var DatafileProjectConfig
70
     */
71
    private $_config;
72

73
    /**
74
     * @var DecisionService
75
     */
76
    private $_decisionService;
77

78
    /**
79
     * @var ErrorHandlerInterface
80
     */
81
    private $_errorHandler;
82

83
    /**
84
     * @var EventDispatcherInterface
85
     */
86
    private $_eventDispatcher;
87

88
    /**
89
     * @var EventBuilder
90
     */
91
    private $_eventBuilder;
92

93
    /**
94
     * @var boolean Denotes whether Optimizely object is valid or not.
95
     */
96
    private $_isValid;
97

98
    /**
99
     * @var LoggerInterface
100
     */
101
    private $_logger;
102

103
    /**
104
     * @var array A default list of options for decision making.
105
     */
106
    private $defaultDecideOptions;
107

108
    /**
109
     * @var ProjectConfigManagerInterface
110
     */
111
    public $configManager;
112

113
    /**
114
     * @var NotificationCenter
115
     */
116
    public $notificationCenter;
117

118
    /**
119
     * Optimizely constructor for managing Feature Experimentation PHP projects.
120
     *
121
     * @param $datafile string JSON string representing the project.
122
     * @param $eventDispatcher EventDispatcherInterface
123
     * @param $logger LoggerInterface
124
     * @param $errorHandler ErrorHandlerInterface
125
     * @param $skipJsonValidation boolean representing whether JSON schema validation needs to be performed.
126
     * @param $userProfileService UserProfileServiceInterface
127
     * @param $configManager ProjectConfigManagerInterface provides ProjectConfig through getConfig method.
128
     * @param $notificationCenter NotificationCenter
129
     * @param $sdkKey string uniquely identifying the datafile corresponding to project and environment combination. Must provide at least one of datafile or sdkKey.
130
     */
131
    public function __construct(
132
        $datafile,
133
        EventDispatcherInterface $eventDispatcher = null,
134
        LoggerInterface $logger = null,
135
        ErrorHandlerInterface $errorHandler = null,
136
        $skipJsonValidation = false,
137
        UserProfileServiceInterface $userProfileService = null,
138
        ProjectConfigManagerInterface $configManager = null,
139
        NotificationCenter $notificationCenter = null,
140
        $sdkKey = null,
141
        array $defaultDecideOptions = []
142
    ) {
143
        $this->_isValid = true;
255✔
144
        $this->_eventDispatcher = $eventDispatcher ?: new DefaultEventDispatcher();
255✔
145
        $this->_logger = $logger ?: new NoOpLogger();
255✔
146
        $this->_errorHandler = $errorHandler ?: new NoOpErrorHandler();
255✔
147
        $this->_eventBuilder = new EventBuilder($this->_logger);
255✔
148
        $this->_decisionService = new DecisionService($this->_logger, $userProfileService);
255✔
149
        $this->notificationCenter = $notificationCenter ?: new NotificationCenter($this->_logger, $this->_errorHandler);
255✔
150
        $this->configManager = $configManager;
255✔
151

152
        if ($this->configManager === null) {
255✔
153
            if ($sdkKey) {
253✔
154
                $this->configManager = new HTTPProjectConfigManager($sdkKey, null, null, true, $datafile, $skipJsonValidation, $this->_logger, $this->_errorHandler, $this->notificationCenter);
2✔
155
            } else {
2✔
156
                $this->configManager = new StaticProjectConfigManager($datafile, $skipJsonValidation, $this->_logger, $this->_errorHandler);
253✔
157
            }
158
        }
253✔
159

160
        $this->defaultDecideOptions = $defaultDecideOptions;
255✔
161
    }
255✔
162

163
    /**
164
     * Returns DatafileProjectConfig instance.
165
     * @return DatafileProjectConfig DatafileProjectConfig instance or null
166
     */
167
    protected function getConfig()
168
    {
169
        $config = $this->configManager->getConfig();
179✔
170
        return $config instanceof DatafileProjectConfig ? $config : null;
179✔
171
    }
172

173
    /**
174
     * Helper function to validate user inputs into the API methods.
175
     *
176
     * @param $attributes array Associative array of user attributes
177
     * @param $eventTags array Hash representing metadata associated with an event.
178
     *
179
     * @return boolean Representing whether all user inputs are valid.
180
     */
181
    private function validateUserInputs($attributes, $eventTags = null)
182
    {
183
        if (!is_null($attributes) && !Validator::areAttributesValid($attributes)) {
164✔
184
            $this->_logger->log(Logger::ERROR, 'Provided attributes are in an invalid format.');
4✔
185
            $this->_errorHandler->handleError(
4✔
186
                new InvalidAttributeException('Provided attributes are in an invalid format.')
4✔
187
            );
4✔
188
            return false;
4✔
189
        }
190

191
        if (!is_null($eventTags)) {
160✔
192
            if (!Validator::areEventTagsValid($eventTags)) {
10✔
193
                $this->_logger->log(Logger::ERROR, 'Provided event tags are in an invalid format.');
2✔
194
                $this->_errorHandler->handleError(
2✔
195
                    new InvalidEventTagException('Provided event tags are in an invalid format.')
2✔
196
                );
2✔
197
                return false;
2✔
198
            }
199
        }
8✔
200

201
        return true;
158✔
202
    }
203

204
    /**
205
     * @param  DatafileProjectConfig DatafileProjectConfig instance
206
     * @param  string        Experiment ID
207
     * @param  string        Variation key
208
     * @param  string        Flag key
209
     * @param  string        Rule key
210
     * @param  string        Rule type
211
     * @param  boolean       Feature enabled
212
     * @param  string        User ID
213
     * @param  array         Associative array of user attributes
214
     */
215
    protected function sendImpressionEvent($config, $experimentId, $variationKey, $flagKey, $ruleKey, $ruleType, $enabled, $userId, $attributes)
216
    {
217
        $experimentKey = $config->getExperimentFromId($experimentId)->getKey();
11✔
218
        $impressionEvent = $this->_eventBuilder
11✔
219
            ->createImpressionEvent($config, $experimentId, $variationKey, $flagKey, $ruleKey, $ruleType, $enabled, $userId, $attributes);
11✔
220
        $this->_logger->log(Logger::INFO, sprintf('Activating user "%s" in experiment "%s".', $userId, $experimentKey));
11✔
221
        $this->_logger->log(
11✔
222
            Logger::DEBUG,
11✔
223
            sprintf(
11✔
224
                'Dispatching impression event to URL %s with params %s.',
11✔
225
                $impressionEvent->getUrl(),
11✔
226
                json_encode($impressionEvent->getParams())
11✔
227
            )
11✔
228
        );
11✔
229

230
        try {
231
            $this->_eventDispatcher->dispatchEvent($impressionEvent);
11✔
232
        } catch (Throwable $exception) {
11✔
233
            $this->_logger->log(
×
234
                Logger::ERROR,
×
235
                sprintf(
×
236
                    'Unable to dispatch impression event. Error %s',
×
237
                    $exception->getMessage()
×
238
                )
×
239
            );
×
240
        } catch (Exception $exception) {
1✔
241
            $this->_logger->log(
1✔
242
                Logger::ERROR,
1✔
243
                sprintf(
1✔
244
                    'Unable to dispatch impression event. Error %s',
1✔
245
                    $exception->getMessage()
1✔
246
                )
1✔
247
            );
1✔
248
        }
249

250
        $this->notificationCenter->sendNotifications(
11✔
251
            NotificationType::ACTIVATE,
11✔
252
            array(
253
                $config->getExperimentFromId($experimentId),
11✔
254
                $userId,
11✔
255
                $attributes,
11✔
256
                $config->getVariationFromKeyByExperimentId($experimentId, $variationKey),
11✔
257
                $impressionEvent
258
            )
11✔
259
        );
11✔
260
    }
11✔
261

262
    /**
263
     * Create a context of the user for which decision APIs will be called.
264
     *
265
     * A user context will be created successfully even when the SDK is not fully configured yet.
266
     *
267
     * @param $userId string The user ID to be used for bucketing.
268
     * @param $userAttributes array A Hash representing user attribute names and values.
269
     *
270
     * @return OptimizelyUserContext|null An OptimizelyUserContext associated with this OptimizelyClient,
271
     *                                    or null If user attributes are not in valid format.
272
     */
273
    public function createUserContext($userId, array $userAttributes = [])
274
    {
275
        // We do not check if config is ready as UserContext can be created even when SDK is not ready.
276

277
        // validate userId
278
        if (!$this->validateInputs(
145✔
279
            [
280
                self::USER_ID => $userId
145✔
281
            ]
145✔
282
        )
145✔
283
        ) {
145✔
284
            return null;
1✔
285
        }
286

287
        // validate attributes
288
        if (!$this->validateUserInputs($userAttributes)) {
144✔
289
            return null;
1✔
290
        }
291

292
        return new OptimizelyUserContext($this, $userId, $userAttributes);
143✔
293
    }
294

295
    /**
296
     * Returns a decision result (OptimizelyDecision) for a given flag key and a user context, which contains all data required to deliver the flag.
297
     *
298
     * If the SDK finds an error, it'll return a `decision` with null for `variationKey`. The decision will include an error message in `reasons`
299
     *
300
     * @param $userContext OptimizelyUserContext context of the user for which decision will be called.
301
     * @param $key string A flag key for which a decision will be made.
302
     * @param $decideOptions array A list of options for decision making.
303
     *
304
     * @return OptimizelyDecision A decision result
305
     */
306
    public function decide(OptimizelyUserContext $userContext, $key, array $decideOptions = [])
307
    {
308
        $decideReasons = [];
36✔
309

310
        // check if SDK is ready
311
        $config = $this->getConfig();
36✔
312
        if ($config === null) {
36✔
313
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
1✔
314
            $decideReasons[] = OptimizelyDecisionMessage::SDK_NOT_READY;
1✔
315
            return new OptimizelyDecision(null, null, null, null, $key, $userContext, $decideReasons);
1✔
316
        }
317

318
        // validate that key is a string
319
        if (!$this->validateInputs(
35✔
320
            [
321
                self::FEATURE_FLAG_KEY => $key
35✔
322
            ]
35✔
323
        )
35✔
324
        ) {
35✔
325
            $errorMessage = sprintf(OptimizelyDecisionMessage::FLAG_KEY_INVALID, $key);
1✔
326
            $this->_logger->log(Logger::ERROR, $errorMessage);
1✔
327
            $decideReasons[] = $errorMessage;
1✔
328
            return new OptimizelyDecision(null, null, null, null, $key, $userContext, $decideReasons);
1✔
329
        }
330

331

332
        // validate that key maps to a feature flag
333
        $featureFlag = $config->getFeatureFlagFromKey($key);
34✔
334
        if ($featureFlag && (!$featureFlag->getId())) {
34✔
335
            // Error logged in DatafileProjectConfig - getFeatureFlagFromKey
336
            $decideReasons[] = sprintf(OptimizelyDecisionMessage::FLAG_KEY_INVALID, $key);
2✔
337
            return new OptimizelyDecision(null, null, null, null, $key, $userContext, $decideReasons);
2✔
338
        }
339

340
        // merge decide options and default decide options
341
        $decideOptions = array_merge($decideOptions, $this->defaultDecideOptions);
32✔
342

343
        // create optimizely decision result
344
        $userId = $userContext->getUserId();
32✔
345
        $userAttributes = $userContext->getAttributes();
32✔
346
        $variationKey = null;
32✔
347
        $featureEnabled = false;
32✔
348
        $ruleKey = null;
32✔
349
        $experimentId = null;
32✔
350
        $flagKey = $key;
32✔
351
        $allVariables = [];
32✔
352
        $decisionEventDispatched = false;
32✔
353

354
        // get decision
355
        $decision = null;
32✔
356
        // check forced-decisions first
357
        $context = new OptimizelyDecisionContext($flagKey, $ruleKey);
32✔
358
        list($forcedDecisionResponse, $reasons) = $this->_decisionService->findValidatedForcedDecision($context, $config, $userContext);
32✔
359
        if ($forcedDecisionResponse) {
32✔
360
            $decision = new FeatureDecision(null, $forcedDecisionResponse, FeatureDecision::DECISION_SOURCE_FEATURE_TEST, $decideReasons);
3✔
361
        } else {
3✔
362
            // regular decision
363
            $decision = $this->_decisionService->getVariationForFeature(
30✔
364
                $config,
30✔
365
                $featureFlag,
30✔
366
                $userContext,
30✔
367
                $decideOptions
368
            );
30✔
369
        }
370
        $decideReasons = array_merge($decideReasons, $reasons);
32✔
371
        $decideReasons = array_merge($decideReasons, $decision->getReasons());
32✔
372
        $variation = $decision->getVariation();
32✔
373

374
        if ($variation) {
32✔
375
            $variationKey = $variation->getKey();
28✔
376
            $featureEnabled = $variation->getFeatureEnabled();
28✔
377
            if ($decision->getExperiment()) {
28✔
378
                $ruleKey = $decision->getExperiment()->getKey();
26✔
379
                $experimentId = $decision->getExperiment()->getId();
26✔
380
            }
26✔
381
        } else {
28✔
382
            $variationKey = null;
8✔
383
            $ruleKey = null;
8✔
384
            $experimentId = null;
8✔
385
        }
386

387
        // send impression only if decide options do not contain DISABLE_DECISION_EVENT
388
        if (!in_array(OptimizelyDecideOption::DISABLE_DECISION_EVENT, $decideOptions)) {
32✔
389
            $sendFlagDecisions = $config->getSendFlagDecisions();
27✔
390
            $source = $decision->getSource();
27✔
391

392
            // send impression for rollout when sendFlagDecisions is enabled.
393
            if ($source == FeatureDecision::DECISION_SOURCE_FEATURE_TEST || $sendFlagDecisions) {
27✔
394
                $this->sendImpressionEvent(
21✔
395
                    $config,
21✔
396
                    $experimentId,
21✔
397
                    $variationKey === null ? '' : $variationKey,
21✔
398
                    $flagKey,
21✔
399
                    $ruleKey === null ? '' : $ruleKey,
21✔
400
                    $source,
21✔
401
                    $featureEnabled,
21✔
402
                    $userId,
21✔
403
                    $userAttributes
404
                );
21✔
405

406
                $decisionEventDispatched = true;
21✔
407
            }
21✔
408
        }
27✔
409

410
        // Generate all variables map if decide options doesn't include excludeVariables
411
        if (!in_array(OptimizelyDecideOption::EXCLUDE_VARIABLES, $decideOptions)) {
32✔
412
            foreach ($featureFlag->getVariables() as $variable) {
28✔
413
                $allVariables[$variable->getKey()] = $this->getFeatureVariableValueFromVariation(
24✔
414
                    $flagKey,
24✔
415
                    $variable->getKey(),
24✔
416
                    null,
24✔
417
                    $featureEnabled,
24✔
418
                    $variation,
24✔
419
                    $userId
420
                );
24✔
421
            }
28✔
422
        }
28✔
423

424
        $shouldIncludeReasons = in_array(OptimizelyDecideOption::INCLUDE_REASONS, $decideOptions);
32✔
425

426
        // send notification
427
        $this->notificationCenter->sendNotifications(
32✔
428
            NotificationType::DECISION,
32✔
429
            array(
430
                DecisionNotificationTypes::FLAG,
32✔
431
                $userId,
32✔
432
                $userAttributes,
32✔
433
                (object) array(
434
                    'flagKey'=> $flagKey,
32✔
435
                    'enabled'=> $featureEnabled,
32✔
436
                    'variables' => $allVariables,
32✔
437
                    'variationKey' => $variationKey,
32✔
438
                    'ruleKey' => $ruleKey,
32✔
439
                    'reasons' => $shouldIncludeReasons ? $decideReasons:[],
32✔
440
                    'decisionEventDispatched' => $decisionEventDispatched
441
                )
32✔
442
            )
32✔
443
        );
32✔
444

445
        return new OptimizelyDecision(
32✔
446
            $variationKey,
32✔
447
            $featureEnabled,
32✔
448
            $allVariables,
32✔
449
            $ruleKey,
32✔
450
            $flagKey,
32✔
451
            $userContext,
32✔
452
            $shouldIncludeReasons ? $decideReasons:[]
32✔
453
        );
32✔
454
    }
455

456
    /**
457
     * Returns a hash of decision results (OptimizelyDecision) for all active flag keys.
458
     *
459
     * @param $userContext OptimizelyUserContext context of the user for which decision will be called.
460
     * @param $decideOptions array A list of options for decision making.
461
     *
462
     * @return array Hash of decisions containing flag keys as hash keys and corresponding decisions as their values.
463
     */
464
    public function decideAll(OptimizelyUserContext $userContext, array $decideOptions = [])
465
    {
466
        // check if SDK is ready
467
        $config = $this->getConfig();
2✔
468
        if ($config === null) {
2✔
469
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
×
470
            return [];
×
471
        }
472

473
        // get all feature keys
474
        $keys = [];
2✔
475
        $featureFlags = $config->getFeatureFlags();
2✔
476
        foreach ($featureFlags as $feature) {
2✔
477
            $keys [] = $feature->getKey();
2✔
478
        }
2✔
479

480
        return $this->decideForKeys($userContext, $keys, $decideOptions);
2✔
481
    }
482

483
    /**
484
     * Returns a hash of decision results (OptimizelyDecision) for multiple flag keys and a user context.
485
     *
486
     * If the SDK finds an error for a key, the response will include a decision for the key showing `reasons` for the error.
487
     *
488
     * The SDK will always return hash of decisions. When it can not process requests, it'll return an empty hash after logging the errors.
489
     *
490
     * @param $userContext OptimizelyUserContext context of the user for which decision will be called.
491
     * @param $keys array A list of flag keys for which the decisions will be made.
492
     * @param $decideOptions array A list of options for decision making.
493
     *
494
     * @return array Hash of decisions containing flag keys as hash keys and corresponding decisions as their values.
495
     */
496
    public function decideForKeys(OptimizelyUserContext $userContext, array $keys, array $decideOptions = [])
497
    {
498
        // check if SDK is ready
499
        $config = $this->getConfig();
4✔
500
        if ($config === null) {
4✔
501
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
×
502
            return [];
×
503
        }
504

505
        // merge decide options and default decide options
506
        $decideOptions = array_merge($decideOptions, $this->defaultDecideOptions);
4✔
507

508
        $enabledFlagsOnly = in_array(OptimizelyDecideOption::ENABLED_FLAGS_ONLY, $decideOptions);
4✔
509
        $decisions = [];
4✔
510

511
        foreach ($keys as $key) {
4✔
512
            $decision = $this->decide($userContext, $key, $decideOptions);
4✔
513
            if (!$enabledFlagsOnly || $decision->getEnabled() === true) {
4✔
514
                $decisions [$key] = $decision;
4✔
515
            }
4✔
516
        }
4✔
517

518
        return $decisions;
4✔
519
    }
520

521
    /**
522
     * Buckets visitor and sends impression event to Optimizely.
523
     *
524
     * @param $experimentKey string Key identifying the experiment.
525
     * @param $userId string ID for user.
526
     * @param $attributes array Attributes of the user.
527
     *
528
     * @return null|string Representing the variation key.
529
     */
530
    public function activate($experimentKey, $userId, $attributes = null)
531
    {
532
        $config = $this->getConfig();
22✔
533
        if ($config === null) {
22✔
534
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
2✔
535
            return null;
2✔
536
        }
537

538
        if (!$this->validateInputs(
21✔
539
            [
540
                self::EXPERIMENT_KEY =>$experimentKey,
21✔
541
                self::USER_ID => $userId
21✔
542
            ]
21✔
543
        )
21✔
544
        ) {
21✔
545
            return null;
2✔
546
        }
547

548
        $variationKey = $this->getVariation($experimentKey, $userId, $attributes);
19✔
549
        if (is_null($variationKey)) {
19✔
550
            $this->_logger->log(Logger::INFO, sprintf('Not activating user "%s".', $userId));
7✔
551
            return null;
7✔
552
        }
553
        $experimentId = $config->getExperimentFromKey($experimentKey)->getId();
12✔
554

555
        $this->sendImpressionEvent($config, $experimentId, $variationKey, '', $experimentKey, FeatureDecision::DECISION_SOURCE_EXPERIMENT, true, $userId, $attributes);
12✔
556

557
        return $variationKey;
12✔
558
    }
559

560
    /**
561
     * Gets OptimizelyConfig object for the current ProjectConfig.
562
     *
563
     * @return OptimizelyConfig Representing current ProjectConfig.
564
     */
565
    public function getOptimizelyConfig()
566
    {
567
        $config = $this->getConfig();
2✔
568
        if ($config === null) {
2✔
569
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
1✔
570
            return null;
1✔
571
        }
572

573
        $optConfigService = new OptimizelyConfigService($config);
1✔
574

575
        return $optConfigService->getConfig();
1✔
576
    }
577

578
    /**
579
     * Send conversion event to Optimizely.
580
     *
581
     * @param $eventKey string Event key representing the event which needs to be recorded.
582
     * @param $userId string ID for user.
583
     * @param $attributes array Attributes of the user.
584
     * @param $eventTags array Hash representing metadata associated with the event.
585
     */
586
    public function track($eventKey, $userId, $attributes = null, $eventTags = null)
587
    {
588
        $config = $this->getConfig();
21✔
589
        if ($config === null) {
21✔
590
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
1✔
591
            return;
1✔
592
        }
593

594
        if (!$this->validateInputs(
20✔
595
            [
596
                self::EVENT_KEY =>$eventKey,
20✔
597
                self::USER_ID => $userId
20✔
598
            ]
20✔
599
        )
20✔
600
        ) {
20✔
601
            return null;
2✔
602
        }
603

604
        if (!$this->validateUserInputs($attributes, $eventTags)) {
18✔
605
            return;
3✔
606
        }
607

608
        $event = $config->getEvent($eventKey);
15✔
609

610
        if (is_null($event->getKey())) {
15✔
611
            $this->_logger->log(Logger::INFO, sprintf('Not tracking user "%s" for event "%s".', $userId, $eventKey));
1✔
612
            return;
1✔
613
        }
614

615
        $conversionEvent = $this->_eventBuilder
14✔
616
            ->createConversionEvent(
14✔
617
                $config,
14✔
618
                $eventKey,
14✔
619
                $userId,
14✔
620
                $attributes,
14✔
621
                $eventTags
622
            );
14✔
623

624
        $this->_logger->log(Logger::INFO, sprintf('Tracking event "%s" for user "%s".', $eventKey, $userId));
14✔
625
        $this->_logger->log(
14✔
626
            Logger::DEBUG,
14✔
627
            sprintf(
14✔
628
                'Dispatching conversion event to URL %s with params %s.',
14✔
629
                $conversionEvent->getUrl(),
14✔
630
                json_encode($conversionEvent->getParams())
14✔
631
            )
14✔
632
        );
14✔
633

634
        try {
635
            $this->_eventDispatcher->dispatchEvent($conversionEvent);
14✔
636
        } catch (Throwable $exception) {
14✔
637
            $this->_logger->log(
×
638
                Logger::ERROR,
×
639
                sprintf(
×
640
                    'Unable to dispatch conversion event. Error %s',
×
641
                    $exception->getMessage()
×
642
                )
×
643
            );
×
644
        } catch (Exception $exception) {
3✔
645
            $this->_logger->log(
3✔
646
                Logger::ERROR,
3✔
647
                sprintf(
3✔
648
                    'Unable to dispatch conversion event. Error %s',
3✔
649
                    $exception->getMessage()
3✔
650
                )
3✔
651
            );
3✔
652
        }
653

654
        $this->notificationCenter->sendNotifications(
14✔
655
            NotificationType::TRACK,
14✔
656
            array(
657
                $eventKey,
14✔
658
                $userId,
14✔
659
                $attributes,
14✔
660
                $eventTags,
14✔
661
                $conversionEvent
662
            )
14✔
663
        );
14✔
664
    }
14✔
665

666
    /**
667
     * Get variation where user will be bucketed.
668
     *
669
     * @param $experimentKey string Key identifying the experiment.
670
     * @param $userId string ID for user.
671
     * @param $attributes array Attributes of the user.
672
     *
673
     * @return null|string Representing the variation key.
674
     */
675
    public function getVariation($experimentKey, $userId, $attributes = null)
676
    {
677
        // TODO: Config should be passed as param when this is called from activate but
678
        // since PHP is single-threaded we can leave this for now.
679
        $config = $this->getConfig();
42✔
680
        if ($config === null) {
42✔
681
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
1✔
682
            return null;
1✔
683
        }
684

685
        if (!$this->validateInputs(
41✔
686
            [
687
                self::EXPERIMENT_KEY =>$experimentKey,
41✔
688
                self::USER_ID => $userId
41✔
689
            ]
41✔
690
        )
41✔
691
        ) {
41✔
692
            return null;
3✔
693
        }
694

695
        $experiment = $config->getExperimentFromKey($experimentKey);
39✔
696

697
        if (is_null($experiment->getKey())) {
39✔
698
            return null;
2✔
699
        }
700

701
        if (!$this->validateUserInputs($attributes)) {
38✔
702
            return null;
2✔
703
        }
704

705
        $userContext = $this->createUserContext($userId, $attributes ? $attributes : []);
36✔
706
        list($variation, $reasons) = $this->_decisionService->getVariation($config, $experiment, $userContext);
36✔
707
        $variationKey = ($variation === null) ? null : $variation->getKey();
36✔
708

709
        if ($config->isFeatureExperiment($experiment->getId())) {
36✔
710
            $decisionNotificationType = DecisionNotificationTypes::FEATURE_TEST;
5✔
711
        } else {
5✔
712
            $decisionNotificationType = DecisionNotificationTypes::AB_TEST;
32✔
713
        }
714

715
        $attributes = $attributes ?: [];
36✔
716
        $this->notificationCenter->sendNotifications(
36✔
717
            NotificationType::DECISION,
36✔
718
            array(
719
                $decisionNotificationType,
36✔
720
                $userId,
36✔
721
                $attributes,
36✔
722
                (object) array(
723
                    'experimentKey'=> $experiment->getKey(),
36✔
724
                    'variationKey'=> $variationKey
725
                )
36✔
726
            )
36✔
727
        );
36✔
728

729
        return $variationKey;
36✔
730
    }
731

732
    /**
733
     * Force a user into a variation for a given experiment.
734
     *
735
     * @param $experimentKey string Key identifying the experiment.
736
     * @param $userId string The user ID to be used for bucketing.
737
     * @param $variationKey string The variation key specifies the variation which the user
738
     * will be forced into. If null, then clear the existing experiment-to-variation mapping.
739
     *
740
     * @return boolean A boolean value that indicates if the set completed successfully.
741
     */
742
    public function setForcedVariation($experimentKey, $userId, $variationKey)
743
    {
744
        $config = $this->getConfig();
20✔
745
        if ($config === null) {
20✔
746
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
×
747
            return false;
×
748
        }
749

750
        if (!$this->validateInputs(
20✔
751
            [
752
                self::EXPERIMENT_KEY =>$experimentKey,
20✔
753
                self::USER_ID => $userId
20✔
754
            ]
20✔
755
        )) {
20✔
756
            return false;
3✔
757
        }
758
        return $this->_decisionService->setForcedVariation($config, $experimentKey, $userId, $variationKey);
19✔
759
    }
760

761
    /**
762
     * Gets the forced variation for a given user and experiment.
763
     *
764
     * @param $experimentKey string Key identifying the experiment.
765
     * @param $userId string The user ID to be used for bucketing.
766
     *
767
     * @return string|null The forced variation key.
768
     */
769
    public function getForcedVariation($experimentKey, $userId)
770
    {
771
        $config = $this->getConfig();
7✔
772
        if ($config === null) {
7✔
773
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
×
774
            return null;
×
775
        }
776

777
        if (!$this->validateInputs(
7✔
778
            [
779
                self::EXPERIMENT_KEY =>$experimentKey,
7✔
780
                self::USER_ID => $userId
7✔
781
            ]
7✔
782
        )) {
7✔
783
            return null;
3✔
784
        }
785

786
        list($forcedVariation, $reasons)  = $this->_decisionService->getForcedVariation($config, $experimentKey, $userId);
6✔
787
        if (isset($forcedVariation)) {
6✔
788
            return $forcedVariation->getKey();
5✔
789
        } else {
790
            return null;
2✔
791
        }
792
    }
793

794
    /**
795
     * Determine whether a feature is enabled.
796
     * Sends an impression event if the user is bucketed into an experiment using the feature.
797
     *
798
     * @param string Feature flag key
799
     * @param string User ID
800
     * @param array Associative array of user attributes
801
     *
802
     * @return boolean
803
     */
804
    public function isFeatureEnabled($featureFlagKey, $userId, $attributes = null)
805
    {
806
        $config = $this->getConfig();
23✔
807
        if ($config === null) {
23✔
808
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
1✔
809
            return false;
1✔
810
        }
811

812
        if (!$this->validateInputs(
22✔
813
            [
814
                self::FEATURE_FLAG_KEY =>$featureFlagKey,
22✔
815
                self::USER_ID => $userId
22✔
816
            ]
22✔
817
        )
22✔
818
        ) {
22✔
819
            return false;
2✔
820
        }
821

822
        $featureFlag = $config->getFeatureFlagFromKey($featureFlagKey);
20✔
823
        if ($featureFlag && (!$featureFlag->getId())) {
20✔
824
            // Error logged in DatafileProjectConfig - getFeatureFlagFromKey
825
            return false;
1✔
826
        }
827

828
        //validate feature flag
829
        if (!Validator::isFeatureFlagValid($config, $featureFlag)) {
19✔
830
            return false;
1✔
831
        }
832

833
        $featureEnabled = false;
18✔
834
        $userContext = $this->createUserContext($userId, $attributes?: []);
18✔
835
        $decision = $this->_decisionService->getVariationForFeature($config, $featureFlag, $userContext);
18✔
836
        $variation = $decision->getVariation();
18✔
837

838
        if ($config->getSendFlagDecisions() && ($decision->getSource() == FeatureDecision::DECISION_SOURCE_ROLLOUT || !$variation)) {
18✔
839
            if ($variation) {
1✔
840
                $featureEnabled = $variation->getFeatureEnabled();
1✔
841
            }
1✔
842
            $ruleKey = $decision->getExperiment() ? $decision->getExperiment()->getKey() : '';
1✔
843
            $experimentId = $decision->getExperiment() ? $decision->getExperiment()->getId() : '';
1✔
844
            $this->sendImpressionEvent($config, $experimentId, $variation ? $variation->getKey() : '', $featureFlagKey, $ruleKey, $decision->getSource(), $featureEnabled, $userId, $attributes);
1✔
845
        }
1✔
846

847
        if ($variation) {
18✔
848
            $experimentKey = $decision->getExperiment()->getKey();
14✔
849
            $experimentId = $decision->getExperiment()->getId();
14✔
850
            $featureEnabled = $variation->getFeatureEnabled();
14✔
851
            if ($decision->getSource() == FeatureDecision::DECISION_SOURCE_FEATURE_TEST) {
14✔
852
                $sourceInfo = (object) array(
853
                    'experimentKey'=> $experimentKey,
6✔
854
                    'variationKey'=> $variation->getKey()
6✔
855
                );
6✔
856

857
                $this->sendImpressionEvent($config, $experimentId, $variation->getKey(), $featureFlagKey, $experimentKey, $decision->getSource(), $featureEnabled, $userId, $attributes);
6✔
858
            } else {
6✔
859
                $this->_logger->log(Logger::INFO, "The user '{$userId}' is not being experimented on Feature Flag '{$featureFlagKey}'.");
9✔
860
            }
861
        }
14✔
862

863
        $attributes = is_null($attributes) ? [] : $attributes;
18✔
864
        $this->notificationCenter->sendNotifications(
18✔
865
            NotificationType::DECISION,
18✔
866
            array(
867
                DecisionNotificationTypes::FEATURE,
18✔
868
                $userId,
18✔
869
                $attributes,
18✔
870
                (object) array(
871
                    'featureKey'=>$featureFlagKey,
18✔
872
                    'featureEnabled'=> $featureEnabled,
18✔
873
                    'source'=> $decision->getSource(),
18✔
874
                    'sourceInfo'=> isset($sourceInfo) ? $sourceInfo : (object) array()
18✔
875
                )
18✔
876
            )
18✔
877
        );
18✔
878

879
        if ($featureEnabled == true) {
18✔
880
            $this->_logger->log(Logger::INFO, "Feature Flag '{$featureFlagKey}' is enabled for user '{$userId}'.");
10✔
881
            return $featureEnabled;
10✔
882
        }
883

884
        $this->_logger->log(Logger::INFO, "Feature Flag '{$featureFlagKey}' is not enabled for user '{$userId}'.");
9✔
885
        return false;
9✔
886
    }
887

888
    /**
889
     * Get keys of all feature flags which are enabled for the user
890
     *
891
     * @param  string User ID
892
     * @param  array Associative array of user attributes
893
     * @return array List of feature flag keys
894
     */
895
    public function getEnabledFeatures($userId, $attributes = null)
896
    {
897
        $enabledFeatureKeys = [];
7✔
898

899
        $config = $this->getConfig();
7✔
900
        if ($config === null) {
7✔
901
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
1✔
902
            return $enabledFeatureKeys;
1✔
903
        }
904

905
        if (!$this->validateInputs(
6✔
906
            [
907
                self::USER_ID => $userId
6✔
908
            ]
6✔
909
        )
6✔
910
        ) {
6✔
911
            return $enabledFeatureKeys;
1✔
912
        }
913

914
        $featureFlags = $config->getFeatureFlags();
5✔
915
        foreach ($featureFlags as $feature) {
5✔
916
            $featureKey = $feature->getKey();
6✔
917
            if ($this->isFeatureEnabled($featureKey, $userId, $attributes) === true) {
5✔
918
                $enabledFeatureKeys[] = $featureKey;
4✔
919
            }
4✔
920
        }
5✔
921

922
        return $enabledFeatureKeys;
5✔
923
    }
924

925
    /**
926
     * Get value of the specified variable in the feature flag.
927
     *
928
     * @param string Feature flag key
929
     * @param string Variable key
930
     * @param string User ID
931
     * @param array  Associative array of user attributes
932
     * @param string Variable type
933
     *
934
     * @return string|boolean|number|array|null Value of the variable cast to the appropriate
935
     *                                          type, or null if the feature key is invalid, the
936
     *                                          variable key is invalid, or there is a mismatch
937
     *                                          with the type of the variable
938
     */
939
    public function getFeatureVariableValueForType(
940
        $featureFlagKey,
941
        $variableKey,
942
        $userId,
943
        $attributes = null,
944
        $variableType = null
945
    ) {
946
        $config = $this->getConfig();
26✔
947
        if ($config === null) {
26✔
948
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, FeatureVariable::getFeatureVariableMethodName($variableType)));
×
949
            return null;
×
950
        }
951

952
        if (!$this->validateInputs(
26✔
953
            [
954
                self::FEATURE_FLAG_KEY => $featureFlagKey,
26✔
955
                self::VARIABLE_KEY => $variableKey,
26✔
956
                self::USER_ID => $userId
26✔
957
            ]
26✔
958
        )
26✔
959
        ) {
26✔
960
            return null;
2✔
961
        }
962

963
        $featureFlag = $config->getFeatureFlagFromKey($featureFlagKey);
24✔
964
        if ($featureFlag && (!$featureFlag->getId())) {
24✔
965
            // Error logged in DatafileProjectConfig - getFeatureFlagFromKey
966
            return null;
1✔
967
        }
968
        $userContext = $this->createUserContext($userId, $attributes? $attributes : []);
23✔
969
        $decision = $this->_decisionService->getVariationForFeature($config, $featureFlag, $userContext);
23✔
970
        $variation = $decision->getVariation();
23✔
971
        $experiment = $decision->getExperiment();
23✔
972
        $featureEnabled = $variation !== null ? $variation->getFeatureEnabled() : false;
23✔
973
        $variableValue = $this->getFeatureVariableValueFromVariation($featureFlagKey, $variableKey, $variableType, $featureEnabled, $variation, $userId);
23✔
974
        // returning as variable not found or type is invalid
975
        if ($variableValue === null) {
23✔
976
            return null;
4✔
977
        }
978
        if ($variation && $decision->getSource() == FeatureDecision::DECISION_SOURCE_FEATURE_TEST) {
19✔
979
            $sourceInfo = (object) array(
980
                'experimentKey'=> $experiment->getKey(),
8✔
981
                'variationKey'=> $variation->getKey()
8✔
982
            );
8✔
983
        }
8✔
984

985
        $attributes = $attributes ?: [];
19✔
986
        $this->notificationCenter->sendNotifications(
19✔
987
            NotificationType::DECISION,
19✔
988
            array(
989
                DecisionNotificationTypes::FEATURE_VARIABLE,
19✔
990
                $userId,
19✔
991
                $attributes,
19✔
992
                (object) array(
993
                    'featureKey'=>$featureFlagKey,
19✔
994
                    'featureEnabled'=> $featureEnabled,
19✔
995
                    'variableKey'=> $variableKey,
19✔
996
                    'variableType'=> $variableType,
19✔
997
                    'variableValue'=> $variableValue,
19✔
998
                    'source'=> $decision->getSource(),
19✔
999
                    'sourceInfo'=> isset($sourceInfo) ? $sourceInfo : (object) array()
19✔
1000
                )
19✔
1001
            )
19✔
1002
        );
19✔
1003

1004
        return $variableValue;
19✔
1005
    }
1006

1007
    /**
1008
     * Get the Boolean value of the specified variable in the feature flag.
1009
     *
1010
     * @param string Feature flag key
1011
     * @param string Variable key
1012
     * @param string User ID
1013
     * @param array  Associative array of user attributes
1014
     *
1015
     * @return string boolean variable value / null
1016
     */
1017
    public function getFeatureVariableBoolean($featureFlagKey, $variableKey, $userId, $attributes = null)
1018
    {
1019
        return $this->getFeatureVariableValueForType(
7✔
1020
            $featureFlagKey,
7✔
1021
            $variableKey,
7✔
1022
            $userId,
7✔
1023
            $attributes,
7✔
1024
            FeatureVariable::BOOLEAN_TYPE
1025
        );
7✔
1026
    }
1027

1028
    /**
1029
     * Get the Integer value of the specified variable in the feature flag.
1030
     *
1031
     * @param string Feature flag key
1032
     * @param string Variable key
1033
     * @param string User ID
1034
     * @param array  Associative array of user attributes
1035
     *
1036
     * @return string integer variable value / null
1037
     */
1038
    public function getFeatureVariableInteger($featureFlagKey, $variableKey, $userId, $attributes = null)
1039
    {
1040
        return $this->getFeatureVariableValueForType(
5✔
1041
            $featureFlagKey,
5✔
1042
            $variableKey,
5✔
1043
            $userId,
5✔
1044
            $attributes,
5✔
1045
            FeatureVariable::INTEGER_TYPE
1046
        );
5✔
1047
    }
1048

1049
    /**
1050
     * Get the Double value of the specified variable in the feature flag.
1051
     *
1052
     * @param string Feature flag key
1053
     * @param string Variable key
1054
     * @param string User ID
1055
     * @param array  Associative array of user attributes
1056
     *
1057
     * @return string double variable value / null
1058
     */
1059
    public function getFeatureVariableDouble($featureFlagKey, $variableKey, $userId, $attributes = null)
1060
    {
1061
        return $this->getFeatureVariableValueForType(
3✔
1062
            $featureFlagKey,
3✔
1063
            $variableKey,
3✔
1064
            $userId,
3✔
1065
            $attributes,
3✔
1066
            FeatureVariable::DOUBLE_TYPE
1067
        );
3✔
1068
    }
1069

1070
    /**
1071
     * Get the String value of the specified variable in the feature flag.
1072
     *
1073
     * @param string Feature flag key
1074
     * @param string Variable key
1075
     * @param string User ID
1076
     * @param array  Associative array of user attributes
1077
     *
1078
     * @return string variable value / null
1079
     */
1080
    public function getFeatureVariableString($featureFlagKey, $variableKey, $userId, $attributes = null)
1081
    {
1082
        return $this->getFeatureVariableValueForType(
4✔
1083
            $featureFlagKey,
4✔
1084
            $variableKey,
4✔
1085
            $userId,
4✔
1086
            $attributes,
4✔
1087
            FeatureVariable::STRING_TYPE
1088
        );
4✔
1089
    }
1090

1091
    /**
1092
    * Get the JSON value of the specified variable in the feature flag.
1093
    *
1094
    * @param string Feature flag key
1095
    * @param string Variable key
1096
    * @param string User ID
1097
    * @param array  Associative array of user attributes
1098
    *
1099
    * @return array Associative array of json variable including key and value
1100
    */
1101
    public function getFeatureVariableJSON($featureFlagKey, $variableKey, $userId, $attributes = null)
1102
    {
1103
        return $this->getFeatureVariableValueForType(
2✔
1104
            $featureFlagKey,
2✔
1105
            $variableKey,
2✔
1106
            $userId,
2✔
1107
            $attributes,
2✔
1108
            FeatureVariable::JSON_TYPE
1109
        );
2✔
1110
    }
1111

1112
    /**
1113
     * Returns values for all the variables attached to the given feature
1114
     *
1115
     * @param string Feature flag key
1116
     * @param string User ID
1117
     * @param array  Associative array of user attributes
1118
     *
1119
     * @return array|null array of all the variables, or null if the feature key is invalid
1120
     */
1121
    public function getAllFeatureVariables($featureFlagKey, $userId, $attributes = null)
1122
    {
1123
        $config = $this->getConfig();
7✔
1124
        if ($config === null) {
7✔
1125
            $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_DATAFILE, __FUNCTION__));
×
1126
            return null;
×
1127
        }
1128

1129
        if (!$this->validateInputs(
7✔
1130
            [
1131
                self::FEATURE_FLAG_KEY => $featureFlagKey,
7✔
1132
                self::USER_ID => $userId
7✔
1133
            ]
7✔
1134
        )
7✔
1135
        ) {
7✔
1136
            return null;
2✔
1137
        }
1138

1139
        $featureFlag = $config->getFeatureFlagFromKey($featureFlagKey);
5✔
1140
        if ($featureFlag && (!$featureFlag->getId())) {
5✔
1141
            return null;
1✔
1142
        }
1143

1144
        $decision = $this->_decisionService->getVariationForFeature($config, $featureFlag, $this->createUserContext($userId, $attributes));
4✔
1145
        $variation = $decision->getVariation();
4✔
1146
        $experiment = $decision->getExperiment();
4✔
1147
        $featureEnabled = $variation !== null ? $variation->getFeatureEnabled() : false;
4✔
1148

1149
        $allVariables = [];
4✔
1150
        foreach ($featureFlag->getVariables() as $variable) {
4✔
1151
            $allVariables[$variable->getKey()] = $this->getFeatureVariableValueFromVariation(
4✔
1152
                $featureFlagKey,
4✔
1153
                $variable->getKey(),
4✔
1154
                null,
4✔
1155
                $featureEnabled,
4✔
1156
                $variation,
4✔
1157
                $userId
1158
            );
4✔
1159
        }
4✔
1160

1161
        $sourceInfo = (object) array();
4✔
1162
        if ($variation && $decision->getSource() == FeatureDecision::DECISION_SOURCE_FEATURE_TEST) {
4✔
1163
            $sourceInfo = (object) array(
1164
                'experimentKey'=> $experiment->getKey(),
3✔
1165
                'variationKey'=> $variation->getKey()
3✔
1166
            );
3✔
1167
        }
3✔
1168

1169
        $attributes = $attributes ?: [];
4✔
1170
        
1171
        $this->notificationCenter->sendNotifications(
4✔
1172
            NotificationType::DECISION,
4✔
1173
            array(
1174
                DecisionNotificationTypes::ALL_FEATURE_VARIABLES,
4✔
1175
                $userId,
4✔
1176
                $attributes,
4✔
1177
                (object)array(
1178
                    'featureKey' => $featureFlagKey,
4✔
1179
                    'featureEnabled' => $featureEnabled,
4✔
1180
                    'variableValues' => $allVariables,
4✔
1181
                    'source' => $decision->getSource(),
4✔
1182
                    'sourceInfo' => $sourceInfo
1183
                )
4✔
1184
            )
4✔
1185
        );
4✔
1186

1187
        return $allVariables;
4✔
1188
    }
1189

1190
    /**
1191
     * Get the value of the specified variable on the basis of its status and usage
1192
     *
1193
     * @param string    Feature flag key
1194
     * @param string    Variable key
1195
     * @param boolean   Feature Status
1196
     * @param Variation for feature
1197
     * @param string    User Id
1198
     *
1199
     * @return string|boolean|number|array|null Value of the variable cast to the appropriate
1200
     *                                          type, or null if the feature key is invalid, the
1201
     *                                          variable key is invalid, or there is a mismatch
1202
     *                                          with the type of the variable
1203
     */
1204
    private function getFeatureVariableValueFromVariation($featureFlagKey, $variableKey, $variableType, $featureEnabled, $variation, $userId)
1205
    {
1206
        $config = $this->getConfig();
51✔
1207
        $variable = $config->getFeatureVariableFromKey($featureFlagKey, $variableKey);
51✔
1208
        if (!$variable) {
51✔
1209
            // Error message logged in ProjectConfigInterface- getFeatureVariableFromKey
1210
            return null;
1✔
1211
        }
1212
        if ($variableType && $variableType != $variable->getType()) {
50✔
1213
            $this->_logger->log(
1✔
1214
                Logger::ERROR,
1✔
1215
                "Variable is of type '{$variable->getType()}', but you requested it as type '{$variableType}'."
1✔
1216
            );
1✔
1217
            return null;
1✔
1218
        }
1219
        $variableValue = $variable->getDefaultValue();
49✔
1220
        if ($variation === null) {
49✔
1221
            $this->_logger->log(
17✔
1222
                Logger::INFO,
17✔
1223
                "User '{$userId}' is not in experiment or rollout, ".
17✔
1224
                "returning default value '{$variableValue}'."
17✔
1225
            );
17✔
1226
        } else {
17✔
1227
            if ($featureEnabled) {
36✔
1228
                $variableUsage = $variation->getVariableUsageById($variable->getId());
29✔
1229
                if ($variableUsage) {
29✔
1230
                    $variableValue = $variableUsage->getValue();
28✔
1231
                    $this->_logger->log(
28✔
1232
                        Logger::INFO,
28✔
1233
                        "Returning variable value '{$variableValue}' for variable key '{$variableKey}' ".
28✔
1234
                        "of feature flag '{$featureFlagKey}'."
28✔
1235
                    );
28✔
1236
                } else {
28✔
1237
                    $this->_logger->log(
2✔
1238
                        Logger::INFO,
2✔
1239
                        "Variable value is not defined. Returning the default variable value '{$variableValue}'."
2✔
1240
                    );
2✔
1241
                }
1242
            } else {
29✔
1243
                $this->_logger->log(
7✔
1244
                    Logger::INFO,
7✔
1245
                    "Feature '{$featureFlagKey}' is not enabled for user '{$userId}'. ".
7✔
1246
                    "Returning the default variable value '{$variableValue}'."
7✔
1247
                );
7✔
1248
            }
1249
        }
1250

1251
        if (!is_null($variableValue)) {
49✔
1252
            $variableValue = VariableTypeUtils::castStringToType($variableValue, $variable->getType(), $this->_logger);
49✔
1253
        }
49✔
1254
        return $variableValue;
49✔
1255
    }
1256

1257
    /**
1258
     * Determine if the instance of the Optimizely client is valid.
1259
     * An instance can be deemed invalid if it was not initialized
1260
     * properly due to an invalid datafile being passed in.
1261
     *
1262
     * @return True if the Optimizely instance is valid.
1263
     *         False if the Optimizely instance is not valid.
1264
     */
1265
    public function isValid()
1266
    {
1267
        if (!$this->getConfig()) {
2✔
1268
            $this->_logger->log(
1✔
1269
                Logger::ERROR,
1✔
1270
                Errors::NO_CONFIG
1271
            );
1✔
1272
            return false;
1✔
1273
        }
1274
        return true;
1✔
1275
    }
1276

1277
    /**
1278
    * Calls Validator::validateNonEmptyString for each value in array
1279
    * Logs for each invalid value
1280
    *
1281
    * @param array values to validate
1282
    * @param logger
1283
    *
1284
    * @return bool True if all of the values are valid, False otherwise
1285
    */
1286
    protected function validateInputs(array $values, $logLevel = Logger::ERROR)
1287
    {
1288
        $isValid = true;
198✔
1289
        if (array_key_exists(self::USER_ID, $values)) {
198✔
1290
            // Empty str is a valid user ID
1291
            if (!is_string($values[self::USER_ID])) {
190✔
1292
                $this->_logger->log(Logger::ERROR, sprintf(Errors::INVALID_FORMAT, self::USER_ID));
12✔
1293
                $isValid = false;
12✔
1294
            }
12✔
1295
            unset($values[self::USER_ID]);
190✔
1296
        }
190✔
1297

1298
        foreach ($values as $key => $value) {
198✔
1299
            if (!Validator::validateNonEmptyString($value)) {
158✔
1300
                $isValid = false;
5✔
1301
                $message = sprintf(Errors::INVALID_FORMAT, $key);
5✔
1302
                $this->_logger->log($logLevel, $message);
5✔
1303
            }
5✔
1304
        }
198✔
1305

1306
        return $isValid;
198✔
1307
    }
1308
}
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