Client::clearCache()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 5
Bugs 0 Features 0
Metric Value
c 5
b 0
f 0
dl 0
loc 10
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 6
nc 2
nop 1
crap 2
1
<?php
2
namespace Werkspot\BingAdsApiBundle\Api;
3
4
use BingAds\Proxy\ClientProxy;
5
use BingAds\Reporting\PollGenerateReportRequest;
6
use BingAds\Reporting\ReportRequest;
7
use BingAds\Reporting\ReportTimePeriod;
8
use BingAds\Reporting\SubmitGenerateReportRequest;
9
use Exception;
10
use SoapFault;
11
use SoapVar;
12
use Werkspot\BingAdsApiBundle\Api\Exceptions\InvalidReportNameException;
13
use Werkspot\BingAdsApiBundle\Api\Helper\Csv;
14
use Werkspot\BingAdsApiBundle\Api\Helper\File;
15
use Werkspot\BingAdsApiBundle\Api\Helper\Time;
16
use Werkspot\BingAdsApiBundle\Api\Report\ReportInterface;
17
use Werkspot\BingAdsApiBundle\Guzzle\OauthTokenService;
18
use Werkspot\BingAdsApiBundle\Model\AccessToken;
19
use Werkspot\BingAdsApiBundle\Model\ApiDetails;
20
21
class Client
22
{
23
    const CACHE_SUBDIRECTORY = 'BingAdsApiBundle';
24
    /**
25
     * @var array
26
     */
27
    private $config = [];
28
29
    /**
30
     * @var string
31
     */
32
    private $fileName;
33
34
    /**
35
     * @var ClientProxy
36
     */
37
    private $proxy;
38
39
    /**
40
     * @var array
41
     */
42
    public $report;
43
44
    /**
45
     * @var string[]
46
     */
47
    private $files;
48
49
    /**
50
     * @var OauthTokenService
51
     */
52
    private $oauthTokenService;
53
54
    /**
55
     * @var ApiDetails
56
     */
57
    private $apiDetails;
58
59
    /**
60
     * @var ClientProxy
61
     */
62
    private $clientProxy;
63
64
    /**
65
     * @var File
66
     */
67
    private $fileHelper;
68
69
    /**
70
     * @var Csv
71
     */
72
    private $csvHelper;
73
74
    /**
75
     * @var Time
76
     */
77
    private $timeHelper;
78
79
    /**
80
     * @param OauthTokenService $oauthTokenService
81
     * @param ApiDetails $apiDetails
82
     * @param ClientProxy $clientProxy
83
     * @param File $file
84
     * @param Csv $csv
85
     * @param Time $timeHelper
86
     */
87
    public function __construct(OauthTokenService $oauthTokenService, ApiDetails $apiDetails, ClientProxy $clientProxy, File $file, Csv $csv, Time $timeHelper)
88
    {
89 25
        $this->oauthTokenService = $oauthTokenService;
90 1
        $this->apiDetails = $apiDetails;
91 25
        $this->clientProxy = $clientProxy;
92 25
        $this->fileHelper = $file;
93 25
        $this->csvHelper = $csv;
94 25
        $this->timeHelper = $timeHelper;
95 25
96 25
        ini_set('soap.wsdl_cache_enabled', '0');
97
        ini_set('soap.wsdl_cache_ttl', '0');
98 25
99 25
        $this->fileName = 'report.zip';
100
101 25
        $this->report = [
102
            'GeoLocationPerformanceReport' => new Report\GeoLocationPerformanceReport(),
103 25
        ];
104 25
    }
105
106 25
    public function setApiDetails(ApiDetails $apiDetails)
107
    {
108 1
        $this->apiDetails = $apiDetails;
109
    }
110 1
111 1
    /**
112
     * Sets the configuration
113
     *
114
     * @param $config
115
     */
116
    public function setConfig($config)
117
    {
118 24
        //TODO: make this specific setter
119
        $this->config = $config;
120 24
        $this->config['cache_dir'] = $this->config['cache_dir'] . '/' . self::CACHE_SUBDIRECTORY; //<-- important for the cache clear function
121 24
        $this->config['csv']['fixHeader']['removeColumnHeader'] = true; //-- fix till i know how to do this
122 24
    }
123 24
124
    public function getRefreshToken()
125 1
    {
126
        return $this->apiDetails->getRefreshToken();
127 1
    }
128
129
    /**
130
     * @param string $reportName
131
     * @param array $columns
132
     * @param $timePeriod
133
     * @param null|string $fileLocation
134
     */
135
    public function getReport($reportName, array $columns, $timePeriod = ReportTimePeriod::LastWeek, $fileLocation = null)
136
    {
137
        $this->ensureValidReportName($reportName);
138 24
        $oauthToken = $this->getOauthToken();
139
        $this->apiDetails->setRefreshToken($oauthToken->getRefreshToken());
140 24
141 24
        $report = $this->report[$reportName];
142 24
        $report->setTimePeriod($timePeriod);
143 24
        $report->setColumns($columns);
144 24
        $reportRequest = $report->getRequest();
145 24
        $this->setProxy($report::WSDL, $oauthToken->getAccessToken());
146
        $files = $this->getFilesFromReportRequest($reportRequest, $reportName, "{$this->getCacheDir()}/{$this->fileName}", $report);
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $this instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
147 24
        if ($fileLocation !== null) {
148 24
            $this->fileHelper->moveFirstFile($files, $fileLocation);
149
        }
150 24
        $this->files = $files;
151 24
    }
152 24
153 24
    /**
154 24
     * @return AccessToken
155 24
     */
156
    protected function getOauthToken()
157 3
    {
158 1
        return  $this->oauthTokenService->refreshToken(
159
            $this->apiDetails->getClientId(),
160 1
            $this->apiDetails->getSecret(),
161
            $this->apiDetails->getRedirectUri(),
162 2
            new AccessToken(null, $this->apiDetails->getRefreshToken())
163
        );
164
    }
165
166
    /**
167
     * @param string $wsdl
168
     * @param string $accessToken
169
     */
170 24
    private function setProxy($wsdl, $accessToken)
171
    {
172 24
        $this->proxy = $this->clientProxy->ConstructWithCredentials($wsdl, null, null, $this->apiDetails->getDevToken(), $accessToken);
173 24
    }
174
175
    /**
176
     * @return string
177
     */
178 24
    private function getCacheDir()
179
    {
180 24
        $this->fileHelper->createDirIfNotExists($this->config['cache_dir']);
181 24
182 1
        return $this->config['cache_dir'];
183 1
    }
184
185 24
    /**
186
     * @param ReportRequest $reportRequest
187
     * @param string $name
188
     * @param string $downloadFile
189
     * @param ReportInterface $report
190
     *
191
     * @throws Exception
192
     *
193
     * @return string[]
194
     */
195
    private function getFilesFromReportRequest(ReportRequest $reportRequest, $name, $downloadFile, ReportInterface $report)
196
    {
197
        $reportRequestId = $this->submitGenerateReport($reportRequest, $name);
198 24
        $reportRequestStatus = $this->waitForStatus($reportRequestId);
199
        $reportDownloadUrl = $reportRequestStatus->ReportDownloadUrl;
200 24
        $file = $this->fileHelper->copyFile($reportDownloadUrl, $downloadFile);
201 6
202 3
        if ($this->fileHelper->isHealthyZipFile($file)) {
203 3
            $files = $this->fixFile($report, $this->fileHelper->unZip($file));
204 3
        } else {
205 3
            $files = [$file];
206 3
        }
207 3
208
        return $files;
209 3
    }
210
211
    /**
212
     * SubmitGenerateReport helper method calls the corresponding Bing Ads service operation
213
     * to request the report identifier. The identifier is used to check report generation status
214
     * before downloading the report.
215
     *
216
     * @param mixed  $report
217
     * @param string $name
218
     *
219
     * @return string ReportRequestId
220
     */
221
    private function submitGenerateReport($report, $name)
222 24
    {
223
        $request = new SubmitGenerateReportRequest();
224 24
        try {
225
            $request->ReportRequest = $this->getReportRequest($report, $name);
226 24
227
            return $this->proxy->GetService()->SubmitGenerateReport($request)->ReportRequestId;
228 24
        } catch (SoapFault $e) {
229 18
            $this->parseSoapFault($e);
230 18
        }
231
    }
232
233
    /**
234
     * @param mixed  $report
235
     * @param string $name
236
     *
237
     * @return SoapVar
238
     */
239
    private function getReportRequest($report, $name)
240 24
    {
241
        $name = "{$name}Request";
242 24
243
        return new SoapVar($report, SOAP_ENC_OBJECT, $name, $this->proxy->GetNamespace());
244 24
    }
245
246
    /**
247
     * Check if the report is ready for download
248
     * if not wait 10 sec and retry. (up to 6,5 hour)
249
     * After 30 tries check every 1 minute
250
     * After 34 tries check every 5 minutes
251
     * After 39 tries check every 15 minutes
252
     * After 43 tries check every 30 minutes
253
     *
254
     * @param string  $reportRequestId
255
     * @param int     $count
256
     * @param int     $maxCount
257
     * @param int     $sleep
258
     * @param bool $incrementTime
259
     *
260
     * @throws Exceptions\ReportRequestErrorException
261
     * @throws Exceptions\RequestTimeoutException
262
     *
263
     * @return string
264
     */
265
    private function waitForStatus($reportRequestId, $count = 1, $maxCount = 48, $sleep = 10, $incrementTime = true)
266 6
    {
267
        if ($count > $maxCount) {
268 6
            throw new Exceptions\RequestTimeoutException("The request is taking longer than expected.\nSave the report ID ({$reportRequestId}) and try again later.");
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $reportRequestId instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
269 1
        }
270
271
        $reportRequestStatus = $this->pollGenerateReport($reportRequestId);
272 6
        if ($reportRequestStatus->Status == 'Pending') {
273 5
            ++$count;
274 1
            $this->timeHelper->sleep($sleep);
275 1
            if ($incrementTime) {
276 1
                switch ($count) {
277
                    case 31: // after 5 minutes
278 1
                        $sleep = (1 * 60);
279 1
                        break;
280 1
                    case 35: // after 10 minutes
281 1
                        $sleep = (5 * 60);
282 1
                        break;
283 1
                    case 40: // after 30 minutes
284 1
                        $sleep = (15 * 60);
285 1
                        break;
286 1
                    case 44: // after 1,5 hours
287 1
                        $sleep = (30 * 60);
288 1
                        break;
289 1
                }
290
            }
291 1
            $reportRequestStatus = $this->waitForStatus($reportRequestId, $count, $maxCount, $sleep, $incrementTime);
292 1
        }
293
294
        if ($reportRequestStatus->Status == 'Error') {
295 4
            throw new Exceptions\ReportRequestErrorException("The request failed. Try requesting the report later.\nIf the request continues to fail, contact support.", $reportRequestStatus->Status, $reportRequestId);
296 1
        }
297
298
        return $reportRequestStatus;
299 3
    }
300
301
    /**
302
     * Check the status of the report request. The guidance of how often to poll
303
     * for status is from every five to 15 minutes depending on the amount
304
     * of data being requested. For smaller reports, you can poll every couple
305
     * of minutes. You should stop polling and try again later if the request
306
     * is taking longer than an hour.
307
     *
308
     * @param string $reportRequestId
309
     *
310
     * @return string ReportRequestStatus
311
     */
312
    private function pollGenerateReport($reportRequestId)
313 6
    {
314
        $request = new PollGenerateReportRequest();
315 6
        $request->ReportRequestId = $reportRequestId;
316 6
        try {
317
            return $this->proxy->GetService()->PollGenerateReport($request)->ReportRequestStatus;
318 6
        } catch (SoapFault $e) {
319 1
            $this->parseSoapFault($e);
320 1
        }
321
    }
322
323
    /**
324
     * @param array|null $files
325
     *
326
     * @return string[]
327
     */
328
    private function fixFile(ReportInterface $report, array $files)
329 3
    {
330
        foreach ($files as $file) {
331 3
            $lines = $this->fileHelper->readFileLinesIntoArray($file);
332 3
333 3
            $lines = $this->csvHelper->removeHeaders($lines, $this->config['csv']['fixHeader']['removeColumnHeader'], $report::FILE_HEADERS, $report::COLUMN_HEADERS);
334 3
            $lines = $this->csvHelper->removeLastLines($lines);
335 3
            $lines = $this->csvHelper->convertDateMDYtoYMD($lines);
336 3
337 3
            $this->fileHelper->writeLinesToFile($lines, $file);
338 3
        }
339 3
340 3
        return $files;
341
    }
342 3
343
344
    /**
345
     * @param bool $allFiles delete all files in bundles cache, if false deletes only extracted files ($this->files)
346
     *
347
     * @return self
348
     */
349
    public function clearCache($allFiles = false)
350
    {
351
        if ($allFiles) {
352 1
            $this->fileHelper->clearCache($this->config['cache_dir']);
353
        } else {
354 1
            $this->fileHelper->clearCache($this->files);
355 1
        }
356
357 1
        return $this;
358
    }
359
360
    /**
361
     * @param SoapFault $e
362
     *
363
     * @throws Exceptions\SoapInternalErrorException
364
     * @throws Exceptions\SoapInvalidCredentialsException
365
     * @throws Exceptions\SoapNoCompleteDataAvailableException
366
     * @throws Exceptions\SoapReportingServiceInvalidReportIdException
367
     * @throws Exceptions\SoapUnknownErrorException
368
     * @throws Exceptions\SoapUserIsNotAuthorizedException
369
     */
370
    private function parseSoapFault(SoapFault $e)
371
    {
372
        $error = null;
373
        if (isset($e->detail->AdApiFaultDetail)) {
374
            $error = $e->detail->AdApiFaultDetail->Errors->AdApiError;
375
        } elseif (isset($e->detail->ApiFaultDetail)) {
376
            if (!empty($e->detail->ApiFaultDetail->BatchErrors)) {
377
                $error = $error = $e->detail->ApiFaultDetail->BatchErrors->BatchError;
378
            } elseif (!empty($e->detail->ApiFaultDetail->OperationErrors)) {
379
                $error = $e->detail->ApiFaultDetail->OperationErrors->OperationError;
380
            }
381
        }
382
        $errors = is_array($error) ? $error : ['error' => $error];
383
        foreach ($errors as $error) {
384
            switch ($error->Code) {
385
                case 0:
386
                    throw new Exceptions\SoapInternalErrorException($error->Message, $error->Code);
387
                case 105:
388
                    throw new Exceptions\SoapInvalidCredentialsException($error->Message, $error->Code);
389
                case 106:
390
                    throw new Exceptions\SoapUserIsNotAuthorizedException($error->Message, $error->Code);
391
                case 2004:
392
                    throw new Exceptions\SoapNoCompleteDataAvailableException($error->Message, $error->Code);
393
                case 2100:
394
                    throw new Exceptions\SoapReportingServiceInvalidReportIdException($error->Message, $error->Code);
395
                default:
396
                    $errorMessage = "[{$error->Code}]\n{$error->Message}";
0 ignored issues
show
Coding Style Best Practice introduced by
As per coding-style, please use concatenation or sprintf for the variable $error instead of interpolation.

It is generally a best practice as it is often more readable to use concatenation instead of interpolation for variables inside strings.

// Instead of
$x = "foo $bar $baz";

// Better use either
$x = "foo " . $bar . " " . $baz;
$x = sprintf("foo %s %s", $bar, $baz);
Loading history...
397 19
                    throw new Exceptions\SoapUnknownErrorException($errorMessage, $error->Code);
398
            }
399 19
        }
400 19
    }
401 7
402 19
    /**
403 12
     * @param $reportName
404 6
     *
405 12
     * @throws InvalidReportNameException
406 6
     */
407 6
    private function ensureValidReportName($reportName)
408 12
    {
409 19
        if ($reportName === '' || $reportName === null) {
410 19
            throw new InvalidReportNameException();
411 19
        }
412 19
    }
413
}
414