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

janu-software / facebook-php-sdk / 13285164661

12 Feb 2025 12:17PM UTC coverage: 93.359%. Remained the same
13285164661

push

github

stanislav-janu
php 8.1 lock file

956 of 1024 relevant lines covered (93.36%)

11.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.56
/src/Facebook.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * Copyright 2017 Facebook, Inc.
6
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to
7
 * use, copy, modify, and distribute this software in source code or binary
8
 * form for use in connection with the web services and APIs provided by
9
 * Facebook.
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
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20
 * DEALINGS IN THE SOFTWARE.
21
 */
22

23
namespace JanuSoftware\Facebook;
24

25
use InvalidArgumentException;
26
use JanuSoftware\Facebook\Authentication\AccessToken;
27
use JanuSoftware\Facebook\Authentication\OAuth2Client;
28
use JanuSoftware\Facebook\Exception\SDKException;
29
use JanuSoftware\Facebook\FileUpload\File;
30
use JanuSoftware\Facebook\FileUpload\ResumableUploader;
31
use JanuSoftware\Facebook\FileUpload\TransferChunk;
32
use JanuSoftware\Facebook\FileUpload\Video;
33
use JanuSoftware\Facebook\GraphNode\GraphEdge;
34
use JanuSoftware\Facebook\Helper\CanvasHelper;
35
use JanuSoftware\Facebook\Helper\JavaScriptHelper;
36
use JanuSoftware\Facebook\Helper\PageTabHelper;
37
use JanuSoftware\Facebook\Helper\RedirectLoginHelper;
38
use JanuSoftware\Facebook\PersistentData\PersistentDataFactory;
39
use JanuSoftware\Facebook\PersistentData\PersistentDataInterface;
40
use JanuSoftware\Facebook\Url\UrlDetectionHandler;
41
use JanuSoftware\Facebook\Url\UrlDetectionInterface;
42
use Psr\Http\Client\ClientInterface;
43
use Safe\Exceptions\FilesystemException;
44
use TypeError;
45

46

47
class Facebook
48
{
49
        /**
50
         * @const string Version number of the Facebook PHP SDK.
51
         */
52
        final public const Version = '0.1';
53

54
        /**
55
         * @const string The name of the environment variable that contains the app ID.
56
         */
57
        final public const AppIdEnvName = 'FACEBOOK_APP_ID';
58

59
        /**
60
         * @const string The name of the environment variable that contains the app secret.
61
         */
62
        final public const AppSecretEnvName = 'FACEBOOK_APP_SECRET';
63

64
        /**
65
         * The Application entity
66
         */
67
        protected Application $app;
68

69
        /**
70
         * The Facebook client service
71
         */
72
        protected Client $client;
73

74
        /**
75
         * The OAuth 2.0 client service.
76
         */
77
        protected ?OAuth2Client $oAuth2Client = null;
78

79
        /**
80
         * The URL detection handler
81
         */
82
        protected null|UrlDetectionInterface $urlDetectionHandler;
83

84
        /**
85
         * The default access token to use with requests
86
         */
87
        protected ?AccessToken $defaultAccessToken = null;
88

89
        /**
90
         * The default Graph version we want to use
91
         */
92
        protected ?string $defaultGraphVersion = null;
93

94
        /**
95
         * The persistent data handler
96
         */
97
        protected ?PersistentDataInterface $persistentDataHandler = null;
98

99
        /**
100
         * Stores the last request made to Graph
101
         */
102
        protected null|BatchResponse|Response $lastResponse = null;
103

104

105
        /**
106
         * Instantiates a new Facebook super-class object.
107
         * @throws SDKException
108
         */
109
        public function __construct(array $config = [])
110
        {
111
                $config = array_merge([
20✔
112
                        'app_id' => getenv(static::AppIdEnvName),
20✔
113
                        'app_secret' => getenv(static::AppSecretEnvName),
20✔
114
                        'default_graph_version' => null,
20✔
115
                        'enable_beta_mode' => false,
20✔
116
                        'http_client' => null,
20✔
117
                        'persistent_data_handler' => null,
20✔
118
                        'url_detection_handler' => null,
20✔
119
                ], $config);
20✔
120

121
                if (!$config['app_id']) {
20✔
122
                        throw new SDKException('Required "app_id" key not supplied in config and could not find fallback environment variable "' . static::AppIdEnvName . '"');
1✔
123
                }
124
                if (!$config['app_secret']) {
19✔
125
                        throw new SDKException('Required "app_secret" key not supplied in config and could not find fallback environment variable "' . static::AppSecretEnvName . '"');
1✔
126
                }
127
                if ($config['http_client'] !== null && !$config['http_client'] instanceof ClientInterface) {
18✔
128
                        throw new InvalidArgumentException('Required "http_client" key to be null or an instance of \Psr\Http\Client\ClientInterface');
2✔
129
                }
130
                if (!$config['default_graph_version']) {
16✔
131
                        throw new InvalidArgumentException('Required "default_graph_version" key not supplied in config');
1✔
132
                }
133

134
                $this->app = new Application($config['app_id'], $config['app_secret']);
15✔
135
                $this->client = new Client($config['http_client'], $config['enable_beta_mode']);
15✔
136
                $this->setUrlDetectionHandler($config['url_detection_handler'] ?? new UrlDetectionHandler);
15✔
137
                $this->persistentDataHandler = PersistentDataFactory::createPersistentDataHandler($config['persistent_data_handler']);
14✔
138

139
                if (isset($config['default_access_token'])) {
13✔
140
                        try {
141
                                $this->setDefaultAccessToken($config['default_access_token']);
3✔
142
                        }
143
                        /**
144
                         * @phpstan-ignore-next-line
145
                         */
146
                        catch (TypeError) {
1✔
147
                                throw new InvalidArgumentException('Key "default_access_token" must be string or class AccessToken');
1✔
148
                        }
149
                }
150

151
                $this->defaultGraphVersion = $config['default_graph_version'];
12✔
152
        }
153

154

155
        /**
156
         * Returns the Application entity.
157
         */
158
        public function getApplication(): Application
159
        {
160
                return $this->app;
3✔
161
        }
162

163

164
        /**
165
         * Returns the Client service.
166
         */
167
        public function getClient(): Client
168
        {
169
                return $this->client;
4✔
170
        }
171

172

173
        /**
174
         * Returns the OAuth 2.0 client service.
175
         */
176
        public function getOAuth2Client(): OAuth2Client
177
        {
178
                if (!$this->oAuth2Client instanceof OAuth2Client) {
2✔
179
                        $application = $this->getApplication();
2✔
180
                        $client = $this->getClient();
2✔
181
                        $this->oAuth2Client = new OAuth2Client($application, $client, $this->defaultGraphVersion);
2✔
182
                }
183

184
                return $this->oAuth2Client;
2✔
185
        }
186

187

188
        /**
189
         * Returns the last response returned from Graph.
190
         * @return BatchResponse|Response|null
191
         */
192
        public function getLastResponse(): ?Response
193
        {
194
                return $this->lastResponse;
1✔
195
        }
196

197

198
        /**
199
         * Returns the URL detection handler.
200
         */
201
        public function getUrlDetectionHandler(): ?UrlDetectionInterface
202
        {
203
                return $this->urlDetectionHandler;
1✔
204
        }
205

206

207
        /**
208
         * Changes the URL detection handler.
209
         */
210
        private function setUrlDetectionHandler(UrlDetectionInterface $urlDetection): void
211
        {
212
                $this->urlDetectionHandler = $urlDetection;
14✔
213
        }
214

215

216
        /**
217
         * Returns the default AccessToken entity.
218
         */
219
        public function getDefaultAccessToken(): ?AccessToken
220
        {
221
                return $this->defaultAccessToken;
2✔
222
        }
223

224

225
        /**
226
         * Sets the default access token to use with requests.
227
         *
228
         * @param AccessToken|string $accessToken the access token to save
229
         *
230
         * @throws InvalidArgumentException
231
         */
232
        public function setDefaultAccessToken(AccessToken|string $accessToken): void
233
        {
234
                $this->defaultAccessToken = is_string($accessToken)
4✔
235
                        ? new AccessToken($accessToken)
3✔
236
                        : $accessToken;
1✔
237
        }
238

239

240
        /**
241
         * Returns the default Graph version.
242
         */
243
        public function getDefaultGraphVersion(): ?string
244
        {
245
                return $this->defaultGraphVersion;
1✔
246
        }
247

248

249
        /**
250
         * Returns the redirect login helper.
251
         */
252
        public function getRedirectLoginHelper(): RedirectLoginHelper
253
        {
254
                return new RedirectLoginHelper($this->getOAuth2Client(), $this->persistentDataHandler, $this->urlDetectionHandler);
2✔
255
        }
256

257

258
        /**
259
         * Returns the JavaScript helper.
260
         */
261
        public function getJavaScriptHelper(): JavaScriptHelper
262
        {
263
                return new JavaScriptHelper($this->app, $this->client, $this->defaultGraphVersion);
1✔
264
        }
265

266

267
        /**
268
         * Returns the canvas helper.
269
         */
270
        public function getCanvasHelper(): CanvasHelper
271
        {
272
                return new CanvasHelper($this->app, $this->client, $this->defaultGraphVersion);
1✔
273
        }
274

275

276
        /**
277
         * Returns the page tab helper.
278
         */
279
        public function getPageTabHelper(): PageTabHelper
280
        {
281
                return new PageTabHelper($this->app, $this->client, $this->defaultGraphVersion);
×
282
        }
283

284

285
        /**
286
         * Sends a GET request to Graph and returns the result.
287
         *
288
         * @throws SDKException
289
         */
290
        public function get(
291
                string $endpoint,
292
                AccessToken|string|null $accessToken = null,
293
                ?string $eTag = null,
294
                ?string $graphVersion = null,
295
        ): Response {
296
                return $this->sendRequest('GET', $endpoint, [], $accessToken, $eTag, $graphVersion);
×
297
        }
298

299

300
        /**
301
         * Sends a POST request to Graph and returns the result.
302
         *
303
         * @throws SDKException
304
         */
305
        public function post(
306
                string $endpoint,
307
                array $params = [],
308
                AccessToken|string|null $accessToken = null,
309
                ?string $eTag = null,
310
                ?string $graphVersion = null,
311
        ): Response {
312
                return $this->sendRequest('POST', $endpoint, $params, $accessToken, $eTag, $graphVersion);
×
313
        }
314

315

316
        /**
317
         * Sends a DELETE request to Graph and returns the result.
318
         *
319
         * @throws SDKException
320
         */
321
        public function delete(
322
                string $endpoint,
323
                array $params = [],
324
                AccessToken|string|null $accessToken = null,
325
                ?string $eTag = null,
326
                ?string $graphVersion = null,
327
        ): Response {
328
                return $this->sendRequest('DELETE', $endpoint, $params, $accessToken, $eTag, $graphVersion);
×
329
        }
330

331

332
        /**
333
         * Sends a request to Graph for the next page of results.
334
         *
335
         * @param GraphEdge $graphEdge the GraphEdge to paginate over
336
         *
337
         * @throws SDKException
338
         */
339
        public function next(GraphEdge $graphEdge): ?GraphEdge
340
        {
341
                return $this->getPaginationResults($graphEdge, 'next');
1✔
342
        }
343

344

345
        /**
346
         * Sends a request to Graph for the previous page of results.
347
         *
348
         * @param GraphEdge $graphEdge the GraphEdge to paginate over
349
         *
350
         * @throws SDKException
351
         */
352
        public function previous(GraphEdge $graphEdge): ?GraphEdge
353
        {
354
                return $this->getPaginationResults($graphEdge, 'previous');
1✔
355
        }
356

357

358
        /**
359
         * Sends a request to Graph for the next page of results.
360
         *
361
         * @param GraphEdge $graphEdge the GraphEdge to paginate over
362
         * @param string    $direction the direction of the pagination: next|previous
363
         *
364
         * @throws SDKException
365
         */
366
        public function getPaginationResults(GraphEdge $graphEdge, string $direction): ?GraphEdge
367
        {
368
                $paginationRequest = $graphEdge->getPaginationRequest($direction);
1✔
369
                if (!$paginationRequest instanceof Request) {
1✔
370
                        return null;
×
371
                }
372

373
                $this->lastResponse = $this->client->sendRequest($paginationRequest);
1✔
374

375
                // Keep the same GraphNode subclass
376
                $subClassName = $graphEdge->getSubClassName();
1✔
377
                $graphEdge = $this->lastResponse->getGraphEdge($subClassName, false);
1✔
378

379
                return $graphEdge->asArray() !== [] ? $graphEdge : null;
1✔
380
        }
381

382

383
        /**
384
         * Sends a request to Graph and returns the result.
385
         *
386
         * @throws SDKException
387
         */
388
        public function sendRequest(
389
                string $method,
390
                string $endpoint,
391
                array $params = [],
392
                AccessToken|string|null $accessToken = null,
393
                ?string $eTag = null,
394
                ?string $graphVersion = null,
395
        ): Response {
396
                $accessToken ??= $this->defaultAccessToken;
×
397
                $graphVersion ??= $this->defaultGraphVersion;
×
398
                $request = $this->request($method, $endpoint, $params, $accessToken, $eTag, $graphVersion);
×
399

400
                return $this->lastResponse = $this->client->sendRequest($request);
×
401
        }
402

403

404
        /**
405
         * Sends a batched request to Graph and returns the result.
406
         *
407
         * @param string|null $graphVersion
408
         * @throws SDKException
409
         */
410
        public function sendBatchRequest(
411
                array $requests,
412
                AccessToken|string|null $accessToken = null,
413
                ?string $graphVersion = null,
414
        ): BatchResponse {
415
                $accessToken ??= $this->defaultAccessToken;
×
416
                $graphVersion ??= $this->defaultGraphVersion;
×
417
                $batchRequest = new BatchRequest($this->app, $requests, $accessToken, $graphVersion);
×
418

419
                return $this->lastResponse = $this->client->sendBatchRequest($batchRequest);
×
420
        }
421

422

423
        /**
424
         * Instantiates an empty BatchRequest entity.
425
         *
426
         * @param AccessToken|string|null $accessToken The top-level access token. Requests with no access token will fallback to this.
427
         * @param string|null $graphVersion the Graph API version to use
428
         */
429
        public function newBatchRequest(
430
                AccessToken|string|null $accessToken = null,
431
                ?string $graphVersion = null,
432
        ): BatchRequest {
433
                $accessToken ??= $this->defaultAccessToken;
1✔
434
                $graphVersion ??= $this->defaultGraphVersion;
1✔
435

436
                return new BatchRequest($this->app, [], $accessToken, $graphVersion);
1✔
437
        }
438

439

440
        /**
441
         * Instantiates a new Request entity.
442
         *
443
         * @param string|null $eTag
444
         * @param string|null $graphVersion
445
         * @throws SDKException
446
         */
447
        public function request(
448
                string $method,
449
                string $endpoint,
450
                array $params = [],
451
                AccessToken|string|null $accessToken = null,
452
                ?string $eTag = null,
453
                ?string $graphVersion = null,
454
        ): Request {
455
                $accessToken ??= $this->defaultAccessToken;
1✔
456
                $graphVersion ??= $this->defaultGraphVersion;
1✔
457

458
                return new Request($this->app, $accessToken, $method, $endpoint, $params, $eTag, $graphVersion);
1✔
459
        }
460

461

462
        /**
463
         * Factory to create File's.
464
         *
465
         * @throws SDKException
466
         */
467
        public function fileToUpload(string $pathToFile): File
468
        {
469
                return new File($pathToFile);
1✔
470
        }
471

472

473
        /**
474
         * Factory to create Video's.
475
         *
476
         * @throws SDKException
477
         */
478
        public function videoToUpload(string $pathToFile): Video
479
        {
480
                return new Video($pathToFile);
2✔
481
        }
482

483

484
        /**
485
         * Upload a video in chunks.
486
         *
487
         * @param int|string                $target             the id of the target node before the /videos edge
488
         * @param string                    $pathToFile         the full path to the file
489
         * @param array                     $metadata           the metadata associated with the video file
490
         * @param string|AccessToken|null   $accessToken        the access token
491
         * @param int                       $maxTransferTries   the max times to retry a failed upload chunk
492
         * @param string|null               $graphVersion       the Graph API version to use
493
         *
494
         * @return array{video_id: int, success: bool}
495
         * @throws SDKException|FilesystemException
496
         */
497
        public function uploadVideo(
498
                int|string $target,
499
                string $pathToFile,
500
                array $metadata = [],
501
                string|AccessToken|null $accessToken = null,
502
                int $maxTransferTries = 5,
503
                ?string $graphVersion = null,
504
        ): array {
505
                $accessToken ??= $this->defaultAccessToken;
2✔
506
                $graphVersion ??= $this->defaultGraphVersion;
2✔
507

508
                $resumableUploader = new ResumableUploader($this->app, $this->client, $accessToken, $graphVersion);
2✔
509
                $endpoint = '/' . $target . '/videos';
2✔
510
                $video = $this->videoToUpload($pathToFile);
2✔
511
                $chunk = $resumableUploader->start($endpoint, $video);
2✔
512

513
                do {
514
                        $chunk = $this->maxTriesTransfer($resumableUploader, $endpoint, $chunk, $maxTransferTries);
2✔
515
                } while (!$chunk->isLastChunk());
1✔
516

517
                return [
1✔
518
                        'video_id' => $chunk->getVideoId(),
1✔
519
                        'success' => $resumableUploader->finish($endpoint, $chunk->getUploadSessionId(), $metadata),
1✔
520
                ];
1✔
521
        }
522

523

524
        /**
525
         * Attempts to upload a chunk of a file in $retryCountdown tries.
526
         *
527
         * @throws SDKException
528
         */
529
        private function maxTriesTransfer(
530
                ResumableUploader $resumableUploader,
531
                string $endpoint,
532
                TransferChunk $transferChunk,
533
                int $retryCountdown,
534
        ): TransferChunk {
535
                $newChunk = $resumableUploader->transfer($endpoint, $transferChunk, $retryCountdown < 1);
2✔
536

537
                if ($newChunk !== $transferChunk) {
2✔
538
                        return $newChunk;
1✔
539
                }
540

541
                $retryCountdown--;
1✔
542

543
                // If transfer() returned the same chunk entity, the transfer failed but is resumable.
544
                return $this->maxTriesTransfer($resumableUploader, $endpoint, $transferChunk, $retryCountdown);
1✔
545
        }
546
}
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

© 2026 Coveralls, Inc