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

optimizely / php-sdk / 4406195320

pending completion
4406195320

Pull #263

github

GitHub
Merge 44031440f into a32e27e0a
Pull Request #263: [FSSDK-8963] Update CHANGELOG.md

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

94.05
/src/Optimizely/ProjectConfigManager/HTTPProjectConfigManager.php
1
<?php
2
/**
3
 * Copyright 2019-2020, 2022 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

18
namespace Optimizely\ProjectConfigManager;
19

20
use Exception;
21
use GuzzleHttp\Client as HttpClient;
22
use Monolog\Logger;
23
use Optimizely\Config\DatafileProjectConfig;
24
use Optimizely\Enums\ProjectConfigManagerConstants;
25
use Optimizely\ErrorHandler\ErrorHandlerInterface;
26
use Optimizely\ErrorHandler\NoOpErrorHandler;
27
use Optimizely\Logger\LoggerInterface;
28
use Optimizely\Logger\NoOpLogger;
29
use Optimizely\Notification\NotificationCenter;
30
use Optimizely\Notification\NotificationType;
31
use Optimizely\Utils\Validator;
32

33
class HTTPProjectConfigManager implements ProjectConfigManagerInterface
34
{
35
    /**
36
     * @var \GuzzleHttp\Client Guzzle HTTP client to send requests.
37
     */
38
    private $httpClient;
39

40
    /**
41
     * @var DatafileProjectConfig
42
     */
43
    private $_config;
44

45
    /**
46
     * @var String Datafile URL.
47
     */
48
    private $_url;
49

50
    /**
51
     * @var boolean Flag indicates that skip JSON validation of datafile.
52
     */
53
    private $_skipJsonValidation;
54

55
    /**
56
     * @var String datafile last modified time.
57
     */
58
    private $_lastModifiedSince;
59

60
    /**
61
     * @var LoggerInterface Logger instance.
62
     */
63
    private $_logger;
64

65
    /**
66
     * @var ErrorHandlerInterface ErrorHandler instance.
67
     */
68
    private $_errorHandler;
69

70
    /**
71
     * @var NotificationCenter NotificationCenter instance.
72
     */
73
    private $_notificationCenter;
74

75
    /**
76
    * @var String datafile access token.
77
    */
78
    private $datafileAccessToken;
79

80
    public function __construct(
81
        $sdkKey = null,
82
        $url = null,
83
        $urlTemplate = null,
84
        $fetchOnInit = true,
85
        $datafile = null,
86
        $skipJsonValidation = false,
87
        LoggerInterface $logger = null,
88
        ErrorHandlerInterface $errorHandler = null,
89
        NotificationCenter $notificationCenter = null,
90
        $datafileAccessToken = null
91
    ) {
92
        $this->_skipJsonValidation = $skipJsonValidation;
25✔
93
        $this->_logger = $logger ?: new NoOpLogger();
25✔
94
        $this->_errorHandler = $errorHandler ?: new NoOpErrorHandler();
25✔
95
        $this->_notificationCenter = $notificationCenter ?: new NotificationCenter($this->_logger, $this->_errorHandler);
25✔
96
        $this->datafileAccessToken = $datafileAccessToken;
25✔
97
        $this->isDatafileAccessTokenValid = Validator::validateNonEmptyString($this->datafileAccessToken);
25✔
98

99
        $this->httpClient = new HttpClient();
25✔
100
        $this->_url = $this->getUrl($sdkKey, $url, $urlTemplate);
25✔
101

102
        if ($datafile !== null) {
24✔
103
            $this->_config = DatafileProjectConfig::createProjectConfigFromDatafile(
15✔
104
                $datafile,
15✔
105
                $skipJsonValidation,
15✔
106
                $this->_logger,
15✔
107
                $this->_errorHandler
15✔
108
            );
15✔
109
        }
15✔
110

111
        // Update config on initialization.
112
        if ($fetchOnInit === true) {
24✔
113
            $this->fetch();
11✔
114
        }
11✔
115
    }
24✔
116

117
    /**
118
     * Helper function to return URL based on params passed.
119
     *
120
     * @param $sdkKey string SDK key.
121
     * @param $url string URL for datafile.
122
     * @param $urlTemplate string Template to be used with SDK key to fetch datafile.
123
     *
124
     * @return string URL for datafile.
125
     */
126
    protected function getUrl($sdkKey, $url, $urlTemplate)
127
    {
128
        if (Validator::validateNonEmptyString($url)) {
25✔
129
            return $url;
12✔
130
        }
131

132
        if (!Validator::validateNonEmptyString($sdkKey)) {
13✔
133
            $exception = new Exception("One of the SDK key or URL must be provided.");
1✔
134
            $this->_errorHandler->handleError($exception);
1✔
135
            throw $exception;
1✔
136
        }
137

138
        if (!Validator::validateNonEmptyString($urlTemplate)) {
12✔
139
            if ($this->isDatafileAccessTokenValid) {
9✔
140
                $urlTemplate = ProjectConfigManagerConstants::AUTHENTICATED_DATAFILE_URL_TEMPLATE;
3✔
141
            } else {
3✔
142
                $urlTemplate = ProjectConfigManagerConstants::DEFAULT_DATAFILE_URL_TEMPLATE;
6✔
143
            }
144
        }
9✔
145

146
        $url = sprintf($urlTemplate, $sdkKey);
12✔
147

148
        return $url;
12✔
149
    }
150

151
    /**
152
     * Function to fetch latest datafile.
153
     *
154
     * @return boolean flag to indicate if datafile is updated.
155
     */
156
    public function fetch()
157
    {
158
        $datafile = $this->fetchDatafile();
14✔
159

160
        if ($datafile === null) {
14✔
161
            return false;
14✔
162
        }
163

164
        return true;
4✔
165
    }
166

167
    /**
168
     * Helper function to fetch datafile and handle response if datafile is modified.
169
     *
170
     * @return null|datafile.
171
     */
172
    protected function fetchDatafile()
173
    {
174
        $headers = [];
9✔
175

176
        // Add If-Modified-Since header.
177
        if (Validator::validateNonEmptyString($this->_lastModifiedSince)) {
9✔
178
            $headers[ProjectConfigManagerConstants::IF_MODIFIED_SINCE] = $this->_lastModifiedSince;
×
179
        }
×
180

181
        // Add Authorization header if access token available.
182
        if ($this->isDatafileAccessTokenValid) {
9✔
183
            $headers['Authorization'] = "Bearer {$this->datafileAccessToken}";
2✔
184
        }
2✔
185

186
        $options = [
187
            'headers' => $headers,
9✔
188
            'timeout' => ProjectConfigManagerConstants::TIMEOUT,
9✔
189
            'connect_timeout' => ProjectConfigManagerConstants::TIMEOUT
190
        ];
9✔
191

192
        try {
193
            $response = $this->httpClient->get($this->_url, $options);
9✔
194
        } catch (Exception $exception) {
9✔
195
            $this->_logger->log(Logger::ERROR, 'Unexpected response when trying to fetch datafile, status code: ' . $exception->getCode(). '. ' .
5✔
196
                'Please check your SDK key and/or datafile access token.');
5✔
197
            return null;
5✔
198
        }
199

200
        $status = $response->getStatusCode();
8✔
201

202
        // Datafile not updated.
203
        if ($status === 304) {
8✔
204
            $this->_logger->log(Logger::DEBUG, 'Not updating ProjectConfig as datafile has not updated since ' . $this->_lastModifiedSince);
1✔
205
            return null;
1✔
206
        }
207

208
        // Datafile retrieved successfully.
209
        if ($status >= 200 && $status < 300) {
7✔
210
            if ($response->hasHeader(ProjectConfigManagerConstants::LAST_MODIFIED)) {
6✔
211
                $this->_lastModifiedSince = $response->getHeader(ProjectConfigManagerConstants::LAST_MODIFIED)[0];
×
212
            }
×
213

214
            $datafile = $response->getBody()->getContents();
6✔
215

216
            if ($this->handleResponse($datafile) === true) {
6✔
217
                return $datafile;
4✔
218
            }
219

220
            return null;
2✔
221
        }
222

223
        // Failed to retrieve datafile from Url.
224
        $this->_logger->log(Logger::ERROR, 'Unexpected response when trying to fetch datafile, status code: ' . $status . '. ' .
1✔
225
            'Please check your SDK key and/or datafile access token.');
1✔
226
        return null;
1✔
227
    }
228

229
    /**
230
     * Helper function to create config from datafile.
231
     *
232
     * @param string $datafile
233
     * @return boolean flag to indicate if config is updated.
234
     */
235
    protected function handleResponse($datafile)
236
    {
237
        if ($datafile === null) {
10✔
238
            return false;
×
239
        }
240

241
        $config = DatafileProjectConfig::createProjectConfigFromDatafile($datafile, $this->_skipJsonValidation, $this->_logger, $this->_errorHandler);
10✔
242
        if ($config === null) {
10✔
243
            return false;
1✔
244
        }
245

246
        $previousRevision = null;
9✔
247
        if ($this->_config !== null) {
9✔
248
            $previousRevision = $this->_config->getRevision();
4✔
249
        }
4✔
250

251
        if ($previousRevision === $config->getRevision()) {
9✔
252
            return false;
1✔
253
        }
254

255
        $this->_config = $config;
8✔
256

257
        $this->_notificationCenter->sendNotifications(NotificationType::OPTIMIZELY_CONFIG_UPDATE);
8✔
258
        $this->_logger->log(Logger::DEBUG, sprintf('Received new datafile and updated config. Old revision number: "%s". New revision number: "%s".', $previousRevision, $this->_config->getRevision()));
8✔
259

260
        return true;
8✔
261
    }
262

263
    /**
264
     * Returns instance of DatafileProjectConfig.
265
     * @return null|DatafileProjectConfig DatafileProjectConfig instance.
266
     */
267
    public function getConfig()
268
    {
269
        return $this->_config;
17✔
270
    }
271
}
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