Facebook   D
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 564
Duplicated Lines 5.85 %

Coupling/Cohesion

Components 1
Dependencies 21

Importance

Changes 10
Bugs 0 Features 0
Metric Value
c 10
b 0
f 0
dl 33
loc 564
rs 4.5442
wmc 46
lcom 1
cbo 21

27 Methods

Rating   Name   Duplication   Size   Complexity  
A setUrlDetectionHandler() 0 4 1
A uploadVideo() 0 19 4
A maxTriesTransfer() 0 13 2
A getApp() 0 4 1
A getClient() 0 4 1
A getOAuth2Client() 0 10 2
A setDefaultAccessToken() 0 16 3
A next() 0 4 1
A previous() 0 4 1
A getPaginationResults() 0 15 3
A getLastResponse() 0 4 1
A getUrlDetectionHandler() 0 4 1
A getDefaultAccessToken() 0 4 1
A getDefaultGraphVersion() 0 4 1
A getRedirectLoginHelper() 0 9 1
A getJavaScriptHelper() 0 4 1
A getCanvasHelper() 0 4 1
A getPageTabHelper() 0 4 1
A get() 11 11 1
A post() 11 11 1
A sendRequest() 0 8 3
A sendBatchRequest() 0 13 3
A request() 0 15 3
A fileToUpload() 0 4 1
A videoToUpload() 0 4 1
A delete() 11 11 1
B __construct() 0 40 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Facebook often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Facebook, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Copyright 2016 Facebook, Inc.
4
 *
5
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to
6
 * use, copy, modify, and distribute this software in source code or binary
7
 * form for use in connection with the web services and APIs provided by
8
 * Facebook.
9
 *
10
 * As with any software that integrates with the Facebook platform, your use
11
 * of this software is subject to the Facebook Developer Principles and
12
 * Policies [http://developers.facebook.com/policy/]. This copyright notice
13
 * shall be included in all copies or substantial portions of the software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
 * DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
namespace Facebook;
25
26
use Facebook\Authentication\AccessToken;
27
use Facebook\Authentication\OAuth2Client;
28
use Facebook\FileUpload\FacebookFile;
29
use Facebook\FileUpload\FacebookResumableUploader;
30
use Facebook\FileUpload\FacebookTransferChunk;
31
use Facebook\FileUpload\FacebookVideo;
32
use Facebook\GraphNodes\GraphEdge;
33
use Facebook\Url\UrlDetectionInterface;
34
use Facebook\Url\FacebookUrlDetectionHandler;
35
use Facebook\PseudoRandomString\PseudoRandomStringGeneratorFactory;
36
use Facebook\PseudoRandomString\PseudoRandomStringGeneratorInterface;
37
use Facebook\HttpClients\HttpClientsFactory;
38
use Facebook\PersistentData\PersistentDataFactory;
39
use Facebook\PersistentData\PersistentDataInterface;
40
use Facebook\Helpers\FacebookCanvasHelper;
41
use Facebook\Helpers\FacebookJavaScriptHelper;
42
use Facebook\Helpers\FacebookPageTabHelper;
43
use Facebook\Helpers\FacebookRedirectLoginHelper;
44
use Facebook\Exceptions\FacebookSDKException;
45
46
/**
47
 * Class Facebook
48
 *
49
 * @package Facebook
50
 */
51
class Facebook
52
{
53
    /**
54
     * @const string Version number of the Facebook PHP SDK.
55
     */
56
    const VERSION = '5.3.1';
57
58
    /**
59
     * @const string Default Graph API version for requests.
60
     */
61
    const DEFAULT_GRAPH_VERSION = 'v2.7';
62
63
    /**
64
     * @const string The name of the environment variable that contains the app ID.
65
     */
66
    const APP_ID_ENV_NAME = 'FACEBOOK_APP_ID';
67
68
    /**
69
     * @const string The name of the environment variable that contains the app secret.
70
     */
71
    const APP_SECRET_ENV_NAME = 'FACEBOOK_APP_SECRET';
72
73
    /**
74
     * @var FacebookApp The FacebookApp entity.
75
     */
76
    protected $app;
77
78
    /**
79
     * @var FacebookClient The Facebook client service.
80
     */
81
    protected $client;
82
83
    /**
84
     * @var OAuth2Client The OAuth 2.0 client service.
85
     */
86
    protected $oAuth2Client;
87
88
    /**
89
     * @var UrlDetectionInterface|null The URL detection handler.
90
     */
91
    protected $urlDetectionHandler;
92
93
    /**
94
     * @var PseudoRandomStringGeneratorInterface|null The cryptographically secure pseudo-random string generator.
95
     */
96
    protected $pseudoRandomStringGenerator;
97
98
    /**
99
     * @var AccessToken|null The default access token to use with requests.
100
     */
101
    protected $defaultAccessToken;
102
103
    /**
104
     * @var string|null The default Graph version we want to use.
105
     */
106
    protected $defaultGraphVersion;
107
108
    /**
109
     * @var PersistentDataInterface|null The persistent data handler.
110
     */
111
    protected $persistentDataHandler;
112
113
    /**
114
     * @var FacebookResponse|FacebookBatchResponse|null Stores the last request made to Graph.
115
     */
116
    protected $lastResponse;
117
118
    /**
119
     * Instantiates a new Facebook super-class object.
120
     *
121
     * @param array $config
122
     *
123
     * @throws FacebookSDKException
124
     */
125
    public function __construct(array $config = [])
126
    {
127
        $config = array_merge([
128
            'app_id' => getenv(static::APP_ID_ENV_NAME),
129
            'app_secret' => getenv(static::APP_SECRET_ENV_NAME),
130
            'default_graph_version' => static::DEFAULT_GRAPH_VERSION,
131
            'enable_beta_mode' => false,
132
            'http_client_handler' => null,
133
            'persistent_data_handler' => null,
134
            'pseudo_random_string_generator' => null,
135
            'url_detection_handler' => null,
136
        ], $config);
137
138
        if (!$config['app_id']) {
139
            throw new FacebookSDKException('Required "app_id" key not supplied in config and could not find fallback environment variable "' . static::APP_ID_ENV_NAME . '"');
140
        }
141
        if (!$config['app_secret']) {
142
            throw new FacebookSDKException('Required "app_secret" key not supplied in config and could not find fallback environment variable "' . static::APP_SECRET_ENV_NAME . '"');
143
        }
144
145
        $this->app = new FacebookApp($config['app_id'], $config['app_secret']);
146
        $this->client = new FacebookClient(
147
            HttpClientsFactory::createHttpClient($config['http_client_handler']),
148
            $config['enable_beta_mode']
149
        );
150
        $this->pseudoRandomStringGenerator = PseudoRandomStringGeneratorFactory::createPseudoRandomStringGenerator(
151
            $config['pseudo_random_string_generator']
152
        );
153
        $this->setUrlDetectionHandler($config['url_detection_handler'] ?: new FacebookUrlDetectionHandler());
154
        $this->persistentDataHandler = PersistentDataFactory::createPersistentDataHandler(
155
            $config['persistent_data_handler']
156
        );
157
158
        if (isset($config['default_access_token'])) {
159
            $this->setDefaultAccessToken($config['default_access_token']);
160
        }
161
162
        // @todo v6: Throw an InvalidArgumentException if "default_graph_version" is not set
163
        $this->defaultGraphVersion = $config['default_graph_version'];
164
    }
165
166
    /**
167
     * Returns the FacebookApp entity.
168
     *
169
     * @return FacebookApp
170
     */
171
    public function getApp()
172
    {
173
        return $this->app;
174
    }
175
176
    /**
177
     * Returns the FacebookClient service.
178
     *
179
     * @return FacebookClient
180
     */
181
    public function getClient()
182
    {
183
        return $this->client;
184
    }
185
186
    /**
187
     * Returns the OAuth 2.0 client service.
188
     *
189
     * @return OAuth2Client
190
     */
191
    public function getOAuth2Client()
192
    {
193
        if (!$this->oAuth2Client instanceof OAuth2Client) {
194
            $app = $this->getApp();
195
            $client = $this->getClient();
196
            $this->oAuth2Client = new OAuth2Client($app, $client, $this->defaultGraphVersion);
197
        }
198
199
        return $this->oAuth2Client;
200
    }
201
202
    /**
203
     * Returns the last response returned from Graph.
204
     *
205
     * @return FacebookResponse|FacebookBatchResponse|null
206
     */
207
    public function getLastResponse()
208
    {
209
        return $this->lastResponse;
210
    }
211
212
    /**
213
     * Returns the URL detection handler.
214
     *
215
     * @return UrlDetectionInterface
216
     */
217
    public function getUrlDetectionHandler()
218
    {
219
        return $this->urlDetectionHandler;
220
    }
221
222
    /**
223
     * Changes the URL detection handler.
224
     *
225
     * @param UrlDetectionInterface $urlDetectionHandler
226
     */
227
    private function setUrlDetectionHandler(UrlDetectionInterface $urlDetectionHandler)
228
    {
229
        $this->urlDetectionHandler = $urlDetectionHandler;
230
    }
231
232
    /**
233
     * Returns the default AccessToken entity.
234
     *
235
     * @return AccessToken|null
236
     */
237
    public function getDefaultAccessToken()
238
    {
239
        return $this->defaultAccessToken;
240
    }
241
242
    /**
243
     * Sets the default access token to use with requests.
244
     *
245
     * @param AccessToken|string $accessToken The access token to save.
246
     *
247
     * @throws \InvalidArgumentException
248
     */
249
    public function setDefaultAccessToken($accessToken)
250
    {
251
        if (is_string($accessToken)) {
252
            $this->defaultAccessToken = new AccessToken($accessToken);
253
254
            return;
255
        }
256
257
        if ($accessToken instanceof AccessToken) {
258
            $this->defaultAccessToken = $accessToken;
259
260
            return;
261
        }
262
263
        throw new \InvalidArgumentException('The default access token must be of type "string" or Facebook\AccessToken');
264
    }
265
266
    /**
267
     * Returns the default Graph version.
268
     *
269
     * @return string
270
     */
271
    public function getDefaultGraphVersion()
272
    {
273
        return $this->defaultGraphVersion;
274
    }
275
276
    /**
277
     * Returns the redirect login helper.
278
     *
279
     * @return FacebookRedirectLoginHelper
280
     */
281
    public function getRedirectLoginHelper()
282
    {
283
        return new FacebookRedirectLoginHelper(
284
            $this->getOAuth2Client(),
285
            $this->persistentDataHandler,
286
            $this->urlDetectionHandler,
287
            $this->pseudoRandomStringGenerator
288
        );
289
    }
290
291
    /**
292
     * Returns the JavaScript helper.
293
     *
294
     * @return FacebookJavaScriptHelper
295
     */
296
    public function getJavaScriptHelper()
297
    {
298
        return new FacebookJavaScriptHelper($this->app, $this->client, $this->defaultGraphVersion);
299
    }
300
301
    /**
302
     * Returns the canvas helper.
303
     *
304
     * @return FacebookCanvasHelper
305
     */
306
    public function getCanvasHelper()
307
    {
308
        return new FacebookCanvasHelper($this->app, $this->client, $this->defaultGraphVersion);
309
    }
310
311
    /**
312
     * Returns the page tab helper.
313
     *
314
     * @return FacebookPageTabHelper
315
     */
316
    public function getPageTabHelper()
317
    {
318
        return new FacebookPageTabHelper($this->app, $this->client, $this->defaultGraphVersion);
319
    }
320
321
    /**
322
     * Sends a GET request to Graph and returns the result.
323
     *
324
     * @param string                  $endpoint
325
     * @param AccessToken|string|null $accessToken
326
     * @param string|null             $eTag
327
     * @param string|null             $graphVersion
328
     *
329
     * @return FacebookResponse
330
     *
331
     * @throws FacebookSDKException
332
     */
333 View Code Duplication
    public function get($endpoint, $accessToken = null, $eTag = null, $graphVersion = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
334
    {
335
        return $this->sendRequest(
336
            'GET',
337
            $endpoint,
338
            $params = [],
339
            $accessToken,
340
            $eTag,
341
            $graphVersion
342
        );
343
    }
344
345
    /**
346
     * Sends a POST request to Graph and returns the result.
347
     *
348
     * @param string                  $endpoint
349
     * @param array                   $params
350
     * @param AccessToken|string|null $accessToken
351
     * @param string|null             $eTag
352
     * @param string|null             $graphVersion
353
     *
354
     * @return FacebookResponse
355
     *
356
     * @throws FacebookSDKException
357
     */
358 View Code Duplication
    public function post($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
359
    {
360
        return $this->sendRequest(
361
            'POST',
362
            $endpoint,
363
            $params,
364
            $accessToken,
365
            $eTag,
366
            $graphVersion
367
        );
368
    }
369
370
    /**
371
     * Sends a DELETE request to Graph and returns the result.
372
     *
373
     * @param string                  $endpoint
374
     * @param array                   $params
375
     * @param AccessToken|string|null $accessToken
376
     * @param string|null             $eTag
377
     * @param string|null             $graphVersion
378
     *
379
     * @return FacebookResponse
380
     *
381
     * @throws FacebookSDKException
382
     */
383 View Code Duplication
    public function delete($endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
384
    {
385
        return $this->sendRequest(
386
            'DELETE',
387
            $endpoint,
388
            $params,
389
            $accessToken,
390
            $eTag,
391
            $graphVersion
392
        );
393
    }
394
395
    /**
396
     * Sends a request to Graph for the next page of results.
397
     *
398
     * @param GraphEdge $graphEdge The GraphEdge to paginate over.
399
     *
400
     * @return GraphEdge|null
401
     *
402
     * @throws FacebookSDKException
403
     */
404
    public function next(GraphEdge $graphEdge)
405
    {
406
        return $this->getPaginationResults($graphEdge, 'next');
407
    }
408
409
    /**
410
     * Sends a request to Graph for the previous page of results.
411
     *
412
     * @param GraphEdge $graphEdge The GraphEdge to paginate over.
413
     *
414
     * @return GraphEdge|null
415
     *
416
     * @throws FacebookSDKException
417
     */
418
    public function previous(GraphEdge $graphEdge)
419
    {
420
        return $this->getPaginationResults($graphEdge, 'previous');
421
    }
422
423
    /**
424
     * Sends a request to Graph for the next page of results.
425
     *
426
     * @param GraphEdge $graphEdge The GraphEdge to paginate over.
427
     * @param string    $direction The direction of the pagination: next|previous.
428
     *
429
     * @return GraphEdge|null
430
     *
431
     * @throws FacebookSDKException
432
     */
433
    public function getPaginationResults(GraphEdge $graphEdge, $direction)
434
    {
435
        $paginationRequest = $graphEdge->getPaginationRequest($direction);
436
        if (!$paginationRequest) {
437
            return null;
438
        }
439
440
        $this->lastResponse = $this->client->sendRequest($paginationRequest);
441
442
        // Keep the same GraphNode subclass
443
        $subClassName = $graphEdge->getSubClassName();
444
        $graphEdge = $this->lastResponse->getGraphEdge($subClassName, false);
445
446
        return count($graphEdge) > 0 ? $graphEdge : null;
447
    }
448
449
    /**
450
     * Sends a request to Graph and returns the result.
451
     *
452
     * @param string                  $method
453
     * @param string                  $endpoint
454
     * @param array                   $params
455
     * @param AccessToken|string|null $accessToken
456
     * @param string|null             $eTag
457
     * @param string|null             $graphVersion
458
     *
459
     * @return FacebookResponse
460
     *
461
     * @throws FacebookSDKException
462
     */
463
    public function sendRequest($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null)
464
    {
465
        $accessToken = $accessToken ?: $this->defaultAccessToken;
466
        $graphVersion = $graphVersion ?: $this->defaultGraphVersion;
467
        $request = $this->request($method, $endpoint, $params, $accessToken, $eTag, $graphVersion);
468
469
        return $this->lastResponse = $this->client->sendRequest($request);
470
    }
471
472
    /**
473
     * Sends a batched request to Graph and returns the result.
474
     *
475
     * @param array                   $requests
476
     * @param AccessToken|string|null $accessToken
477
     * @param string|null             $graphVersion
478
     *
479
     * @return FacebookBatchResponse
480
     *
481
     * @throws FacebookSDKException
482
     */
483
    public function sendBatchRequest(array $requests, $accessToken = null, $graphVersion = null)
484
    {
485
        $accessToken = $accessToken ?: $this->defaultAccessToken;
486
        $graphVersion = $graphVersion ?: $this->defaultGraphVersion;
487
        $batchRequest = new FacebookBatchRequest(
488
            $this->app,
489
            $requests,
490
            $accessToken,
491
            $graphVersion
492
        );
493
494
        return $this->lastResponse = $this->client->sendBatchRequest($batchRequest);
495
    }
496
497
    /**
498
     * Instantiates a new FacebookRequest entity.
499
     *
500
     * @param string                  $method
501
     * @param string                  $endpoint
502
     * @param array                   $params
503
     * @param AccessToken|string|null $accessToken
504
     * @param string|null             $eTag
505
     * @param string|null             $graphVersion
506
     *
507
     * @return FacebookRequest
508
     *
509
     * @throws FacebookSDKException
510
     */
511
    public function request($method, $endpoint, array $params = [], $accessToken = null, $eTag = null, $graphVersion = null)
512
    {
513
        $accessToken = $accessToken ?: $this->defaultAccessToken;
514
        $graphVersion = $graphVersion ?: $this->defaultGraphVersion;
515
516
        return new FacebookRequest(
517
            $this->app,
518
            $accessToken,
519
            $method,
520
            $endpoint,
521
            $params,
522
            $eTag,
523
            $graphVersion
524
        );
525
    }
526
527
    /**
528
     * Factory to create FacebookFile's.
529
     *
530
     * @param string $pathToFile
531
     *
532
     * @return FacebookFile
533
     *
534
     * @throws FacebookSDKException
535
     */
536
    public function fileToUpload($pathToFile)
537
    {
538
        return new FacebookFile($pathToFile);
539
    }
540
541
    /**
542
     * Factory to create FacebookVideo's.
543
     *
544
     * @param string $pathToFile
545
     *
546
     * @return FacebookVideo
547
     *
548
     * @throws FacebookSDKException
549
     */
550
    public function videoToUpload($pathToFile)
551
    {
552
        return new FacebookVideo($pathToFile);
553
    }
554
555
    /**
556
     * Upload a video in chunks.
557
     *
558
     * @param int $target The id of the target node before the /videos edge.
559
     * @param string $pathToFile The full path to the file.
560
     * @param array $metadata The metadata associated with the video file.
561
     * @param string|null $accessToken The access token.
562
     * @param int $maxTransferTries The max times to retry a failed upload chunk.
563
     * @param string|null $graphVersion The Graph API version to use.
564
     *
565
     * @return array
566
     *
567
     * @throws FacebookSDKException
568
     */
569
    public function uploadVideo($target, $pathToFile, $metadata = [], $accessToken = null, $maxTransferTries = 5, $graphVersion = null)
570
    {
571
        $accessToken = $accessToken ?: $this->defaultAccessToken;
572
        $graphVersion = $graphVersion ?: $this->defaultGraphVersion;
573
574
        $uploader = new FacebookResumableUploader($this->app, $this->client, $accessToken, $graphVersion);
575
        $endpoint = '/'.$target.'/videos';
576
        $file = $this->videoToUpload($pathToFile);
577
        $chunk = $uploader->start($endpoint, $file);
578
579
        do {
580
            $chunk = $this->maxTriesTransfer($uploader, $endpoint, $chunk, $maxTransferTries);
581
        } while (!$chunk->isLastChunk());
582
583
        return [
584
          'video_id' => $chunk->getVideoId(),
585
          'success' => $uploader->finish($endpoint, $chunk->getUploadSessionId(), $metadata),
586
        ];
587
    }
588
589
    /**
590
     * Attempts to upload a chunk of a file in $retryCountdown tries.
591
     *
592
     * @param FacebookResumableUploader $uploader
593
     * @param string $endpoint
594
     * @param FacebookTransferChunk $chunk
595
     * @param int $retryCountdown
596
     *
597
     * @return FacebookTransferChunk
598
     *
599
     * @throws FacebookSDKException
600
     */
601
    private function maxTriesTransfer(FacebookResumableUploader $uploader, $endpoint, FacebookTransferChunk $chunk, $retryCountdown)
602
    {
603
        $newChunk = $uploader->transfer($endpoint, $chunk, $retryCountdown < 1);
604
605
        if ($newChunk !== $chunk) {
606
            return $newChunk;
607
        }
608
609
        $retryCountdown--;
610
611
        // If transfer() returned the same chunk entity, the transfer failed but is resumable.
612
        return $this->maxTriesTransfer($uploader, $endpoint, $chunk, $retryCountdown);
613
    }
614
}
615