From 26dd1a07320a2b58b3a44e2caa9a38be3df28130 Mon Sep 17 00:00:00 2001
From: Owais Akbani <oakbani@folio3.com>
Date: Fri, 24 Jan 2020 15:33:55 +0500
Subject: [PATCH 1/3] feat: Add init with sdk key

---
 src/Optimizely/Optimizely.php | 17 ++++++--
 tests/OptimizelyTest.php      | 79 ++++++++++++++++++++++++++++++++---
 2 files changed, 87 insertions(+), 9 deletions(-)

diff --git a/src/Optimizely/Optimizely.php b/src/Optimizely/Optimizely.php
index 43dc7815..b9cb7ed4 100644
--- a/src/Optimizely/Optimizely.php
+++ b/src/Optimizely/Optimizely.php
@@ -37,6 +37,7 @@
 use Optimizely\Notification\NotificationCenter;
 use Optimizely\Notification\NotificationType;
 use Optimizely\OptimizelyConfig\OptimizelyConfigService;
+use Optimizely\ProjectConfigManager\HTTPProjectConfigManager;
 use Optimizely\ProjectConfigManager\ProjectConfigManagerInterface;
 use Optimizely\ProjectConfigManager\StaticProjectConfigManager;
 use Optimizely\UserProfile\UserProfileServiceInterface;
@@ -96,7 +97,7 @@ class Optimizely
     /**
      * @var ProjectConfigManagerInterface
      */
-    private $_projectConfigManager;
+    public $configManager;
 
     /**
      * @var NotificationCenter
@@ -112,6 +113,7 @@ class Optimizely
      * @param $errorHandler ErrorHandlerInterface
      * @param $skipJsonValidation boolean representing whether JSON schema validation needs to be performed.
      * @param $userProfileService UserProfileServiceInterface
+     * @param $sdkKey string uniquely identifying the datafile corresponding to project and environment combination. Must provide at least one of datafile or sdkKey.
      * @param $configManager ProjectConfigManagerInterface provides ProjectConfig through getConfig method.
      * @param $notificationCenter NotificationCenter
      */
@@ -122,6 +124,7 @@ public function __construct(
         ErrorHandlerInterface $errorHandler = null,
         $skipJsonValidation = false,
         UserProfileServiceInterface $userProfileService = null,
+        $sdkKey = null,
         ProjectConfigManagerInterface $configManager = null,
         NotificationCenter $notificationCenter = null
     ) {
@@ -132,7 +135,15 @@ public function __construct(
         $this->_eventBuilder = new EventBuilder($this->_logger);
         $this->_decisionService = new DecisionService($this->_logger, $userProfileService);
         $this->notificationCenter = $notificationCenter ?: new NotificationCenter($this->_logger, $this->_errorHandler);
-        $this->_projectConfigManager = $configManager ?: new StaticProjectConfigManager($datafile, $skipJsonValidation, $this->_logger, $this->_errorHandler);
+        $this->configManager = $configManager;
+
+        if ($this->configManager === null) {
+            if ($sdkKey) {
+                $this->configManager = new HTTPProjectConfigManager($sdkKey, null, null, true, $datafile, $skipJsonValidation, $this->_logger, $this->_errorHandler, $this->notificationCenter);
+            } else {
+                $this->configManager = new StaticProjectConfigManager($datafile, $skipJsonValidation, $this->_logger, $this->_errorHandler);
+            }
+        }
     }
 
     /**
@@ -141,7 +152,7 @@ public function __construct(
      */
     protected function getConfig()
     {
-        $config = $this->_projectConfigManager->getConfig();
+        $config = $this->configManager->getConfig();
         return $config instanceof DatafileProjectConfig ? $config : null;
     }
 
diff --git a/tests/OptimizelyTest.php b/tests/OptimizelyTest.php
index 5d82f508..531e37cf 100644
--- a/tests/OptimizelyTest.php
+++ b/tests/OptimizelyTest.php
@@ -17,6 +17,11 @@
 namespace Optimizely\Tests;
 
 use Exception;
+use GuzzleHttp\Client;
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Middleware;
+use GuzzleHttp\Psr7\Response;
 use Monolog\Logger;
 use Optimizely\Config\DatafileProjectConfig;
 use Optimizely\DecisionService\DecisionService;
@@ -63,9 +68,7 @@ private function setOptimizelyConfigObject($optimizely, $config, $configManager)
         $projConfig->setAccessible(true);
         $projConfig->setValue($configManager, $config);
 
-        $projConfigManager = new \ReflectionProperty(Optimizely::class, '_projectConfigManager');
-        $projConfigManager->setAccessible(true);
-        $projConfigManager->setValue($optimizely, $configManager);
+        $optimizely->configManager = $configManager;
     }
 
     public function setUp()
@@ -221,6 +224,72 @@ public function testInitDatafileInvalidFormat()
         $this->expectOutputRegex('/Provided datafile is in an invalid format./');
     }
 
+    public function testInitWithSdkKey()
+    {
+        $sdkKey = "some-sdk-key";
+        $optimizelyClient = new Optimizely(null, null, null, null, null, null, $sdkKey);
+
+        // client hasn't been mocked yet. Hence, activate should return null.
+        $actualVariation = $optimizelyClient->activate('test_experiment_integer_feature', 'test_user');
+        $this->assertNull($actualVariation);
+
+        // Mock http client to return a valid datafile
+        $mock = new MockHandler([
+            new Response(200, [], $this->datafile)
+        ]);
+
+        $container = [];
+        $history = Middleware::history($container);
+        $handler = HandlerStack::create($mock);
+        $handler->push($history);
+
+        $client = new Client(['handler' => $handler]);
+        $httpClient = new \ReflectionProperty(HTTPProjectConfigManager::class, 'httpClient');
+        $httpClient->setAccessible(true);
+        $httpClient->setValue($optimizelyClient->configManager, $client);
+
+        // Fetch datafile
+        $optimizelyClient->configManager->fetch();
+
+        // activate should return expected variation.
+        $actualVariation = $optimizelyClient->activate('test_experiment_integer_feature', 'test_user');
+        $this->assertEquals('variation', $actualVariation);
+
+        // assert that https call is made to mock as expected.
+        $transaction = $container[0];
+        $this->assertEquals(
+            'https://cdn.optimizely.com/datafiles/some-sdk-key.json',
+            $transaction['request']->getUri()
+        );
+    }
+
+    public function testInitWithBothSdkKeyAndDatafile()
+    {
+        $sdkKey = "some-sdk-key";
+        $optimizelyClient = new Optimizely(DATAFILE, null, null, null, null, null, $sdkKey);
+
+        // client hasn't been mocked yet. Hence, config manager should return config of hardcoded
+        // datafile.
+        $this->assertEquals('15', $optimizelyClient->configManager->getConfig()->getRevision());
+        
+
+        // Mock http client to return a valid datafile
+        $mock = new MockHandler([
+            new Response(200, [], $this->typedAudiencesDataFile)
+        ]);
+
+        $handler = HandlerStack::create($mock);
+        $client = new Client(['handler' => $handler]);
+        $httpClient = new \ReflectionProperty(HTTPProjectConfigManager::class, 'httpClient');
+        $httpClient->setAccessible(true);
+        $httpClient->setValue($optimizelyClient->configManager, $client);
+
+        // Fetch datafile
+        $optimizelyClient->configManager->fetch();
+
+        $this->assertEquals('3', $optimizelyClient->configManager->getConfig()->getRevision());
+    }
+
     public function testActivateInvalidOptimizelyObject()
     {
         $optimizelyMock = $this->getMockBuilder(Optimizely::class)
@@ -4471,9 +4540,7 @@ public function testGetConfigReturnsDatafileProjectConfigInstance()
              ->setMethods(array('getConfig'))
              ->getMock();
 
-         $projectConfigManager = new \ReflectionProperty(Optimizely::class, '_projectConfigManager');
-         $projectConfigManager->setAccessible(true);
-         $projectConfigManager->setValue($optlyObject, $projectConfigManagerMock);
+         $optlyObject->configManager = $projectConfigManagerMock;
 
          $expectedProjectConfig = new DatafileProjectConfig($this->datafile, $this->loggerMock, new NoOpErrorHandler());
 

From f56015a4d21a24b423ff6f8b7b0dcdfe450fb788 Mon Sep 17 00:00:00 2001
From: Owais Akbani <owais.akbani92@gmail.com>
Date: Fri, 20 Mar 2020 14:50:58 +0500
Subject: [PATCH 2/3] refact: Make sdkKey last param of constructor

---
 src/Optimizely/Optimizely.php |  6 +++---
 tests/OptimizelyTest.php      | 24 ++++++++++++++++++++++--
 2 files changed, 25 insertions(+), 5 deletions(-)

diff --git a/src/Optimizely/Optimizely.php b/src/Optimizely/Optimizely.php
index b9cb7ed4..e4b798b7 100644
--- a/src/Optimizely/Optimizely.php
+++ b/src/Optimizely/Optimizely.php
@@ -113,9 +113,9 @@ class Optimizely
      * @param $errorHandler ErrorHandlerInterface
      * @param $skipJsonValidation boolean representing whether JSON schema validation needs to be performed.
      * @param $userProfileService UserProfileServiceInterface
-     * @param $sdkKey string uniquely identifying the datafile corresponding to project and environment combination. Must provide at least one of datafile or sdkKey.
      * @param $configManager ProjectConfigManagerInterface provides ProjectConfig through getConfig method.
      * @param $notificationCenter NotificationCenter
+     * @param $sdkKey string uniquely identifying the datafile corresponding to project and environment combination. Must provide at least one of datafile or sdkKey.
      */
     public function __construct(
         $datafile,
@@ -124,9 +124,9 @@ public function __construct(
         ErrorHandlerInterface $errorHandler = null,
         $skipJsonValidation = false,
         UserProfileServiceInterface $userProfileService = null,
-        $sdkKey = null,
         ProjectConfigManagerInterface $configManager = null,
-        NotificationCenter $notificationCenter = null
+        NotificationCenter $notificationCenter = null,
+        $sdkKey = null
     ) {
         $this->_isValid = true;
         $this->_eventDispatcher = $eventDispatcher ?: new DefaultEventDispatcher();
diff --git a/tests/OptimizelyTest.php b/tests/OptimizelyTest.php
index 531e37cf..79c8f1de 100644
--- a/tests/OptimizelyTest.php
+++ b/tests/OptimizelyTest.php
@@ -227,7 +227,17 @@ public function testInitDatafileInvalidFormat()
     public function testInitWithSdkKey()
     {
         $sdkKey = "some-sdk-key";
-        $optimizelyClient = new Optimizely(null, null, null, null, null, null, $sdkKey);
+        $optimizelyClient = new Optimizely(
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            $sdkKey
+        );
 
         // client hasn't been mocked yet. Hence, activate should return null.
         $actualVariation = $optimizelyClient->activate('test_experiment_integer_feature', 'test_user');
@@ -266,7 +276,17 @@ public function testInitWithSdkKey()
     public function testInitWithBothSdkKeyAndDatafile()
     {
         $sdkKey = "some-sdk-key";
-        $optimizelyClient = new Optimizely(DATAFILE, null, null, null, null, null, $sdkKey);
+        $optimizelyClient = new Optimizely(
+            DATAFILE,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            $sdkKey
+        );
 
         // client hasn't been mocked yet. Hence, config manager should return config of hardcoded
         // datafile.

From 1b8d6edd2615afa836dbdace04ef6058f140c4a6 Mon Sep 17 00:00:00 2001
From: Owais Akbani <owais.akbani92@gmail.com>
Date: Fri, 20 Mar 2020 15:52:10 +0500
Subject: [PATCH 3/3] feat: Add defaultinstance in OptimizelyFactory

---
 src/Optimizely/OptimizelyFactory.php | 42 +++++++++++++++++++
 tests/OptimizelyFactoryTest.php      | 60 ++++++++++++++++++++++++++++
 2 files changed, 102 insertions(+)
 create mode 100644 src/Optimizely/OptimizelyFactory.php
 create mode 100644 tests/OptimizelyFactoryTest.php

diff --git a/src/Optimizely/OptimizelyFactory.php b/src/Optimizely/OptimizelyFactory.php
new file mode 100644
index 00000000..5c1207f0
--- /dev/null
+++ b/src/Optimizely/OptimizelyFactory.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * Copyright 2020, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+namespace Optimizely;
+
+use Optimizely\Optimizely;
+
+/**
+ * Class OptimizelyFactory
+ *
+ * @package Optimizely
+ */
+class OptimizelyFactory
+{
+    public static function createDefaultInstance($sdkKey, $fallbackDatafile = null)
+    {
+        return new Optimizely(
+            $fallbackDatafile,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            null,
+            $sdkKey
+        );
+    }
+}
diff --git a/tests/OptimizelyFactoryTest.php b/tests/OptimizelyFactoryTest.php
new file mode 100644
index 00000000..e0413030
--- /dev/null
+++ b/tests/OptimizelyFactoryTest.php
@@ -0,0 +1,60 @@
+<?php
+/**
+ * Copyright 2020, Optimizely
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+namespace Optimizely\Tests;
+
+use Exception;
+use GuzzleHttp\Client;
+use GuzzleHttp\Handler\MockHandler;
+use GuzzleHttp\HandlerStack;
+use GuzzleHttp\Psr7\Response;
+use Optimizely\OptimizelyFactory;
+use Optimizely\ProjectConfigManager\HTTPProjectConfigManager;
+
+class OptimizelyFactoryTest extends \PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        $this->datafile = DATAFILE;
+        $this->typedAudiencesDataFile = DATAFILE_WITH_TYPED_AUDIENCES;
+    }
+
+    public function testDefaultInstance()
+    {
+        $optimizelyClient = OptimizelyFactory::createDefaultInstance("some-sdk-key", $this->datafile);
+
+        // client hasn't been mocked yet. Hence, config manager should return config of hardcoded
+        // datafile.
+        $this->assertEquals('15', $optimizelyClient->configManager->getConfig()->getRevision());
+
+        // Mock http client to return a valid datafile
+        $mock = new MockHandler([
+            new Response(200, [], $this->typedAudiencesDataFile)
+        ]);
+
+        $handler = HandlerStack::create($mock);
+
+        $client = new Client(['handler' => $handler]);
+        $httpClient = new \ReflectionProperty(HTTPProjectConfigManager::class, 'httpClient');
+        $httpClient->setAccessible(true);
+        $httpClient->setValue($optimizelyClient->configManager, $client);
+
+        /// Fetch datafile
+        $optimizelyClient->configManager->fetch();
+
+        $this->assertEquals('3', $optimizelyClient->configManager->getConfig()->getRevision());
+    }
+}