These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
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 Symfony\Component\Filesystem\Filesystem; |
||
13 | use Symfony\Component\Finder\Finder; |
||
14 | use Werkspot\BingAdsApiBundle\Api\Exceptions\InvalidReportNameException; |
||
15 | use Werkspot\BingAdsApiBundle\Api\Helper\Csv; |
||
16 | use Werkspot\BingAdsApiBundle\Api\Helper\File; |
||
17 | use Werkspot\BingAdsApiBundle\Api\Helper\Time; |
||
18 | use Werkspot\BingAdsApiBundle\Api\Report\ReportInterface; |
||
19 | use Werkspot\BingAdsApiBundle\Guzzle\OauthTokenService; |
||
20 | use Werkspot\BingAdsApiBundle\Model\AccessToken; |
||
21 | use Werkspot\BingAdsApiBundle\Model\ApiDetails; |
||
22 | |||
23 | class Client |
||
24 | { |
||
25 | const CACHE_SUBDIRECTORY = 'BingAdsApiBundle'; |
||
26 | /** |
||
27 | * @var array |
||
28 | */ |
||
29 | private $config = []; |
||
30 | /** |
||
31 | * @var string |
||
32 | */ |
||
33 | private $fileName; |
||
34 | |||
35 | /** |
||
36 | * @var ClientProxy |
||
37 | */ |
||
38 | private $proxy; |
||
39 | |||
40 | /** |
||
41 | * @var array |
||
42 | */ |
||
43 | public $report; |
||
44 | |||
45 | /** |
||
46 | * @var string[] |
||
47 | */ |
||
48 | private $files; |
||
49 | |||
50 | /** |
||
51 | * @var OauthTokenService |
||
52 | */ |
||
53 | private $oauthTokenService; |
||
54 | |||
55 | /** |
||
56 | * @var ApiDetails |
||
57 | */ |
||
58 | private $apiDetails; |
||
59 | |||
60 | /** |
||
61 | * @var ClientProxy |
||
62 | */ |
||
63 | private $clientProxy; |
||
64 | |||
65 | /** |
||
66 | * @var File |
||
67 | */ |
||
68 | private $fileHelper; |
||
69 | |||
70 | /** |
||
71 | * @var Csv |
||
72 | */ |
||
73 | private $csvHelper; |
||
74 | |||
75 | /** |
||
76 | * @var Time |
||
77 | */ |
||
78 | private $timeHelper; |
||
79 | |||
80 | /** |
||
81 | * @var Filesystem |
||
82 | */ |
||
83 | private $filesystem; |
||
84 | |||
85 | /** |
||
86 | * @param OauthTokenService $oauthTokenService |
||
87 | * @param ApiDetails $apiDetails |
||
88 | * @param ClientProxy $clientProxy |
||
89 | 25 | * @param File $file |
|
90 | 1 | * @param Csv $csv |
|
91 | 25 | * @param Time $timeHelper |
|
92 | 25 | * @param Filesystem $fileSystem |
|
93 | 25 | */ |
|
94 | 25 | public function __construct(OauthTokenService $oauthTokenService, ApiDetails $apiDetails, ClientProxy $clientProxy, File $file, Csv $csv, Time $timeHelper, Filesystem $fileSystem) |
|
95 | 25 | { |
|
96 | 25 | $this->filesystem = $fileSystem; |
|
97 | $this->oauthTokenService = $oauthTokenService; |
||
98 | 25 | $this->apiDetails = $apiDetails; |
|
99 | 25 | $this->clientProxy = $clientProxy; |
|
100 | $this->fileHelper = $file; |
||
101 | 25 | $this->csvHelper = $csv; |
|
102 | $this->timeHelper = $timeHelper; |
||
103 | 25 | ||
104 | 25 | ini_set('soap.wsdl_cache_enabled', '0'); |
|
105 | ini_set('soap.wsdl_cache_ttl', '0'); |
||
106 | 25 | ||
107 | $this->fileName = 'report.zip'; |
||
108 | 1 | ||
109 | $this->report = [ |
||
110 | 1 | 'GeoLocationPerformanceReport' => new Report\GeoLocationPerformanceReport(), |
|
111 | 1 | ]; |
|
112 | } |
||
113 | |||
114 | public function setApiDetails(ApiDetails $apiDetails) |
||
115 | { |
||
116 | $this->apiDetails = $apiDetails; |
||
117 | } |
||
118 | 24 | ||
119 | /** |
||
120 | 24 | * Sets the configuration |
|
121 | 24 | * |
|
122 | 24 | * @param $config |
|
123 | 24 | */ |
|
124 | public function setConfig($config) |
||
125 | 1 | { |
|
126 | //TODO: make this specific setter |
||
127 | 1 | $this->config = $config; |
|
128 | $this->config['cache_dir'] = $this->config['cache_dir'] . '/' . self::CACHE_SUBDIRECTORY; //<-- important for the cache clear function |
||
129 | $this->config['csv']['fixHeader']['removeColumnHeader'] = true; //-- fix till i know how to do this |
||
130 | } |
||
131 | |||
132 | public function getRefreshToken() |
||
133 | { |
||
134 | return $this->apiDetails->getRefreshToken(); |
||
135 | } |
||
136 | |||
137 | /** |
||
138 | 24 | * @param string $reportName |
|
139 | * @param array $columns |
||
140 | 24 | * @param $timePeriod |
|
141 | 24 | * @param null|string $fileLocation |
|
142 | 24 | */ |
|
143 | 24 | public function getReport($reportName, array $columns, $timePeriod = ReportTimePeriod::LastWeek, $fileLocation) |
|
144 | 24 | { |
|
145 | 24 | $this->ensureValidReportName($reportName); |
|
146 | $oauthToken = $this->getOauthToken(); |
||
147 | 24 | $this->apiDetails->setRefreshToken($oauthToken->getRefreshToken()); |
|
148 | 24 | ||
149 | $report = $this->report[$reportName]; |
||
150 | 24 | $report->setTimePeriod($timePeriod); |
|
151 | 24 | $report->setColumns($columns); |
|
152 | 24 | $reportRequest = $report->getRequest(); |
|
153 | 24 | $this->setProxy($report::WSDL, $oauthToken->getAccessToken()); |
|
154 | 24 | $files = $this->getFilesFromReportRequest($reportRequest, $reportName, "{$this->getCacheDir()}/{$this->fileName}", $report); |
|
155 | 24 | ||
156 | $this->moveFirstFile($files, $fileLocation); |
||
0 ignored issues
–
show
|
|||
157 | 3 | $this->files = $files; |
|
0 ignored issues
–
show
It seems like
$files can also be of type false . However, the property $files is declared as type array<integer,string> . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
158 | 1 | } |
|
159 | |||
160 | 1 | /** |
|
161 | * @return AccessToken |
||
162 | 2 | */ |
|
163 | protected function getOauthToken() |
||
164 | { |
||
165 | return $this->oauthTokenService->refreshToken( |
||
166 | $this->apiDetails->getClientId(), |
||
167 | $this->apiDetails->getSecret(), |
||
168 | $this->apiDetails->getRedirectUri(), |
||
169 | new AccessToken(null, $this->apiDetails->getRefreshToken()) |
||
170 | 24 | ); |
|
171 | } |
||
172 | 24 | ||
173 | 24 | /** |
|
174 | * @param string $wsdl |
||
175 | * @param string $accessToken |
||
176 | */ |
||
177 | private function setProxy($wsdl, $accessToken) |
||
178 | 24 | { |
|
179 | $this->proxy = $this->clientProxy->ConstructWithCredentials($wsdl, null, null, $this->apiDetails->getDevToken(), $accessToken); |
||
180 | 24 | } |
|
181 | 24 | ||
182 | 1 | /** |
|
183 | 1 | * @return string |
|
184 | */ |
||
185 | 24 | private function getCacheDir() |
|
186 | { |
||
187 | $this->createCacheDirIfNotExists(); |
||
188 | |||
189 | return $this->config['cache_dir']; |
||
190 | } |
||
191 | |||
192 | /** |
||
193 | * @param ReportRequest $reportRequest |
||
194 | * @param string $name |
||
195 | * @param string $downloadFile |
||
196 | * @param ReportInterface $report |
||
197 | * |
||
198 | 24 | * @throws Exception |
|
199 | * |
||
200 | 24 | * @return array|string |
|
201 | 6 | */ |
|
202 | 3 | private function getFilesFromReportRequest(ReportRequest $reportRequest, $name, $downloadFile, ReportInterface $report) |
|
203 | 3 | { |
|
204 | 3 | $reportRequestId = $this->submitGenerateReport($reportRequest, $name); |
|
205 | 3 | $reportRequestStatus = $this->waitForStatus($reportRequestId); |
|
206 | 3 | $reportDownloadUrl = $reportRequestStatus->ReportDownloadUrl; |
|
207 | 3 | $zipFile = $this->fileHelper->copyFile($reportDownloadUrl, $downloadFile); |
|
208 | if ($zipFile !== false) { |
||
209 | 3 | $files = $this->fixFile($report, $this->fileHelper->unZip($zipFile)); |
|
0 ignored issues
–
show
It seems like
$zipFile defined by $this->fileHelper->copyF...loadUrl, $downloadFile) on line 207 can also be of type boolean ; however, Werkspot\BingAdsApiBundle\Api\Helper\File::unZip() does only seem to accept string , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
210 | } else { |
||
211 | $files = $zipFile; |
||
212 | } |
||
213 | |||
214 | return $files; |
||
215 | } |
||
216 | |||
217 | /** |
||
218 | * SubmitGenerateReport helper method calls the corresponding Bing Ads service operation |
||
219 | * to request the report identifier. The identifier is used to check report generation status |
||
220 | * before downloading the report. |
||
221 | * |
||
222 | 24 | * @param mixed $report |
|
223 | * @param string $name |
||
224 | 24 | * |
|
225 | * @return string ReportRequestId |
||
226 | 24 | */ |
|
227 | private function submitGenerateReport($report, $name) |
||
228 | 24 | { |
|
229 | 18 | $request = new SubmitGenerateReportRequest(); |
|
230 | 18 | try { |
|
231 | $request->ReportRequest = $this->getReportRequest($report, $name); |
||
232 | |||
233 | return $this->proxy->GetService()->SubmitGenerateReport($request)->ReportRequestId; |
||
234 | } catch (SoapFault $e) { |
||
235 | $this->parseSoapFault($e); |
||
236 | } |
||
237 | } |
||
238 | |||
239 | /** |
||
240 | 24 | * @param mixed $report |
|
241 | * @param string $name |
||
242 | 24 | * |
|
243 | * @return SoapVar |
||
244 | 24 | */ |
|
245 | private function getReportRequest($report, $name) |
||
246 | { |
||
247 | $name = "{$name}Request"; |
||
248 | |||
249 | return new SoapVar($report, SOAP_ENC_OBJECT, $name, $this->proxy->GetNamespace()); |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Check if the report is ready for download |
||
254 | * if not wait 10 sec and retry. (up to 6,5 hour) |
||
255 | * After 30 tries check every 1 minute |
||
256 | * After 34 tries check every 5 minutes |
||
257 | * After 39 tries check every 15 minutes |
||
258 | * After 43 tries check every 30 minutes |
||
259 | * |
||
260 | * @param string $reportRequestId |
||
261 | * @param int $count |
||
262 | * @param int $maxCount |
||
263 | * @param int $sleep |
||
264 | * @param bool $incrementTime |
||
265 | * |
||
266 | 6 | * @throws Exceptions\ReportRequestErrorException |
|
267 | * @throws Exceptions\RequestTimeoutException |
||
268 | 6 | * |
|
269 | 1 | * @return string |
|
270 | */ |
||
271 | private function waitForStatus($reportRequestId, $count = 1, $maxCount = 48, $sleep = 10, $incrementTime = true) |
||
272 | 6 | { |
|
273 | 5 | if ($count > $maxCount) { |
|
274 | 1 | throw new Exceptions\RequestTimeoutException("The request is taking longer than expected.\nSave the report ID ({$reportRequestId}) and try again later."); |
|
275 | 1 | } |
|
276 | 1 | ||
277 | $reportRequestStatus = $this->pollGenerateReport($reportRequestId); |
||
278 | 1 | if ($reportRequestStatus->Status == 'Pending') { |
|
279 | 1 | ++$count; |
|
280 | 1 | $this->timeHelper->sleep($sleep); |
|
281 | 1 | if ($incrementTime) { |
|
282 | 1 | switch ($count) { |
|
283 | 1 | case 31: // after 5 minutes |
|
284 | 1 | $sleep = (1 * 60); |
|
285 | 1 | break; |
|
286 | 1 | case 35: // after 10 minutes |
|
287 | 1 | $sleep = (5 * 60); |
|
288 | 1 | break; |
|
289 | 1 | case 40: // after 30 minutes |
|
290 | $sleep = (15 * 60); |
||
291 | 1 | break; |
|
292 | 1 | case 44: // after 1,5 hours |
|
293 | $sleep = (30 * 60); |
||
294 | break; |
||
295 | 4 | } |
|
296 | 1 | } |
|
297 | $reportRequestStatus = $this->waitForStatus($reportRequestId, $count, $maxCount, $sleep, $incrementTime); |
||
298 | } |
||
299 | 3 | ||
300 | if ($reportRequestStatus->Status == 'Error') { |
||
301 | throw new Exceptions\ReportRequestErrorException("The request failed. Try requesting the report later.\nIf the request continues to fail, contact support.", $reportRequestStatus->Status, $reportRequestId); |
||
302 | } |
||
303 | |||
304 | return $reportRequestStatus; |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Check the status of the report request. The guidance of how often to poll |
||
309 | * for status is from every five to 15 minutes depending on the amount |
||
310 | * of data being requested. For smaller reports, you can poll every couple |
||
311 | * of minutes. You should stop polling and try again later if the request |
||
312 | * is taking longer than an hour. |
||
313 | 6 | * |
|
314 | * @param string $reportRequestId |
||
315 | 6 | * |
|
316 | 6 | * @return string ReportRequestStatus |
|
317 | */ |
||
318 | 6 | private function pollGenerateReport($reportRequestId) |
|
319 | 1 | { |
|
320 | 1 | $request = new PollGenerateReportRequest(); |
|
321 | $request->ReportRequestId = $reportRequestId; |
||
322 | try { |
||
323 | return $this->proxy->GetService()->PollGenerateReport($request)->ReportRequestStatus; |
||
324 | } catch (SoapFault $e) { |
||
325 | $this->parseSoapFault($e); |
||
326 | } |
||
327 | } |
||
328 | |||
329 | 3 | /** |
|
330 | * @param array|null $files |
||
331 | 3 | * |
|
332 | 3 | * @return string[] |
|
333 | 3 | */ |
|
334 | 3 | private function fixFile(ReportInterface $report, array $files) |
|
335 | 3 | { |
|
336 | 3 | foreach ($files as $file) { |
|
337 | 3 | $lines = $this->fileHelper->readFileLinesIntoArray($file); |
|
338 | 3 | ||
339 | 3 | $lines = $this->csvHelper->removeHeaders($lines, $this->config['csv']['fixHeader']['removeColumnHeader'], $report::FILE_HEADERS, $report::COLUMN_HEADERS); |
|
340 | 3 | $lines = $this->csvHelper->removeLastLines($lines); |
|
341 | $lines = $this->csvHelper->convertDateMDYtoYMD($lines); |
||
342 | 3 | ||
343 | $this->fileHelper->writeLinesToFile($lines, $file); |
||
344 | } |
||
345 | |||
346 | return $files; |
||
347 | } |
||
348 | |||
349 | /** |
||
350 | * @param array $files |
||
351 | * @param string $target |
||
352 | 1 | */ |
|
353 | private function moveFirstFile(array $files, $target) |
||
354 | 1 | { |
|
355 | 1 | $fs = $this->getFileSystem(); |
|
356 | $fs->rename($files[0], $target); |
||
357 | 1 | } |
|
358 | |||
359 | /** |
||
360 | * Clear Bundle Cache directory |
||
361 | * |
||
362 | * @param bool $allFiles delete all files in bundles cache, if false deletes only extracted files ($this->files) |
||
363 | * |
||
364 | * @return self |
||
365 | * |
||
366 | * @codeCoverageIgnore |
||
367 | */ |
||
368 | public function clearCache($allFiles = false) |
||
369 | { |
||
370 | $fileSystem = $this->getFileSystem(); |
||
371 | |||
372 | if ($allFiles) { |
||
373 | $finder = new Finder(); |
||
374 | $files = $finder->files()->in($this->config['cache_dir']); |
||
375 | } else { |
||
376 | $files = $this->files; |
||
377 | } |
||
378 | |||
379 | foreach ($files as $file) { |
||
380 | $fileSystem->remove($file); |
||
381 | } |
||
382 | |||
383 | return $this; |
||
384 | } |
||
385 | |||
386 | /** |
||
387 | * @param SoapFault $e |
||
388 | * |
||
389 | * @throws Exceptions\SoapInternalErrorException |
||
390 | * @throws Exceptions\SoapInvalidCredentialsException |
||
391 | * @throws Exceptions\SoapNoCompleteDataAvailableException |
||
392 | * @throws Exceptions\SoapReportingServiceInvalidReportIdException |
||
393 | * @throws Exceptions\SoapUnknownErrorException |
||
394 | * @throws Exceptions\SoapUserIsNotAuthorizedException |
||
395 | */ |
||
396 | private function parseSoapFault(SoapFault $e) |
||
397 | 19 | { |
|
398 | $error = null; |
||
399 | 19 | if (isset($e->detail->AdApiFaultDetail)) { |
|
400 | 19 | $error = $e->detail->AdApiFaultDetail->Errors->AdApiError; |
|
401 | 7 | } elseif (isset($e->detail->ApiFaultDetail)) { |
|
402 | 19 | if (!empty($e->detail->ApiFaultDetail->BatchErrors)) { |
|
403 | 12 | $error = $error = $e->detail->ApiFaultDetail->BatchErrors->BatchError; |
|
404 | 6 | } elseif (!empty($e->detail->ApiFaultDetail->OperationErrors)) { |
|
405 | 12 | $error = $e->detail->ApiFaultDetail->OperationErrors->OperationError; |
|
406 | 6 | } |
|
407 | 6 | } |
|
408 | 12 | $errors = is_array($error) ? $error : ['error' => $error]; |
|
409 | 19 | foreach ($errors as $error) { |
|
410 | 19 | switch ($error->Code) { |
|
411 | 19 | case 0: |
|
412 | 19 | throw new Exceptions\SoapInternalErrorException($error->Message, $error->Code); |
|
413 | 4 | case 105: |
|
414 | 15 | throw new Exceptions\SoapInvalidCredentialsException($error->Message, $error->Code); |
|
415 | 3 | case 106: |
|
416 | 12 | throw new Exceptions\SoapUserIsNotAuthorizedException($error->Message, $error->Code); |
|
417 | 3 | case 2004: |
|
418 | 9 | throw new Exceptions\SoapNoCompleteDataAvailableException($error->Message, $error->Code); |
|
419 | 3 | case 2100: |
|
420 | 6 | throw new Exceptions\SoapReportingServiceInvalidReportIdException($error->Message, $error->Code); |
|
421 | 3 | default: |
|
422 | 3 | $errorMessage = "[{$error->Code}]\n{$error->Message}"; |
|
423 | 3 | throw new Exceptions\SoapUnknownErrorException($errorMessage, $error->Code); |
|
424 | 3 | } |
|
425 | 3 | } |
|
426 | } |
||
427 | |||
428 | /** |
||
429 | * @param $reportName |
||
430 | * |
||
431 | * @throws InvalidReportNameException |
||
432 | */ |
||
433 | private function ensureValidReportName($reportName) |
||
434 | { |
||
435 | if ($reportName === '') { |
||
436 | throw new InvalidReportNameException(); |
||
437 | } |
||
438 | } |
||
439 | |||
440 | /** |
||
441 | * @return Filesystem |
||
442 | */ |
||
443 | private function getFileSystem() |
||
444 | { |
||
445 | return $this->filesystem; |
||
446 | } |
||
447 | |||
448 | private function createCacheDirIfNotExists() |
||
449 | { |
||
450 | $fs = $this->getFileSystem(); |
||
451 | |||
452 | if (!$fs->exists($this->config['cache_dir'])) { |
||
453 | $fs->mkdir($this->config['cache_dir'], 0700); |
||
454 | } |
||
455 | } |
||
456 | } |
||
457 |
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.