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 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 |
||
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) |
||
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) |
|
|
|||
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) |
|
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) |
|
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) |
||
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) |
||
614 | } |
||
615 |
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.