FacebookBatchRequest   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 267
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 3

Importance

Changes 0
Metric Value
dl 0
loc 267
rs 9.3999
c 0
b 0
f 0
wmc 33
lcom 1
cbo 3

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
B add() 0 29 5
B addFallbackDefaults() 0 18 5
A extractFileAttachments() 0 19 3
A getRequests() 0 4 1
A prepareRequestsForBatch() 0 10 1
A convertRequestsToJson() 0 10 3
A validateBatchRequestCount() 0 10 3
B requestEntityToBatchArray() 0 35 5
A getIterator() 0 4 1
A offsetSet() 0 4 1
A offsetExists() 0 4 1
A offsetUnset() 0 4 1
A offsetGet() 0 4 2
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 ArrayIterator;
27
use IteratorAggregate;
28
use ArrayAccess;
29
use Facebook\Authentication\AccessToken;
30
use Facebook\Exceptions\FacebookSDKException;
31
32
/**
33
 * Class BatchRequest
34
 *
35
 * @package Facebook
36
 */
37
class FacebookBatchRequest extends FacebookRequest implements IteratorAggregate, ArrayAccess
38
{
39
    /**
40
     * @var array An array of FacebookRequest entities to send.
41
     */
42
    protected $requests;
43
44
    /**
45
     * @var array An array of files to upload.
46
     */
47
    protected $attachedFiles;
48
49
    /**
50
     * Creates a new Request entity.
51
     *
52
     * @param FacebookApp|null        $app
53
     * @param array                   $requests
54
     * @param AccessToken|string|null $accessToken
55
     * @param string|null             $graphVersion
56
     */
57
    public function __construct(FacebookApp $app = null, array $requests = [], $accessToken = null, $graphVersion = null)
58
    {
59
        parent::__construct($app, $accessToken, 'POST', '', [], null, $graphVersion);
60
61
        $this->add($requests);
62
    }
63
64
    /**
65
     * A a new request to the array.
66
     *
67
     * @param FacebookRequest|array $request
68
     * @param string|null           $name
69
     *
70
     * @return FacebookBatchRequest
71
     *
72
     * @throws \InvalidArgumentException
73
     */
74
    public function add($request, $name = null)
75
    {
76
        if (is_array($request)) {
77
            foreach ($request as $key => $req) {
78
                $this->add($req, $key);
79
            }
80
81
            return $this;
82
        }
83
84
        if (!$request instanceof FacebookRequest) {
85
            throw new \InvalidArgumentException('Argument for add() must be of type array or FacebookRequest.');
86
        }
87
88
        $this->addFallbackDefaults($request);
89
        $requestToAdd = [
90
            'name' => $name,
91
            'request' => $request,
92
        ];
93
94
        // File uploads
95
        $attachedFiles = $this->extractFileAttachments($request);
96
        if ($attachedFiles) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $attachedFiles of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
97
            $requestToAdd['attached_files'] = $attachedFiles;
98
        }
99
        $this->requests[] = $requestToAdd;
100
101
        return $this;
102
    }
103
104
    /**
105
     * Ensures that the FacebookApp and access token fall back when missing.
106
     *
107
     * @param FacebookRequest $request
108
     *
109
     * @throws FacebookSDKException
110
     */
111
    public function addFallbackDefaults(FacebookRequest $request)
112
    {
113
        if (!$request->getApp()) {
114
            $app = $this->getApp();
115
            if (!$app) {
116
                throw new FacebookSDKException('Missing FacebookApp on FacebookRequest and no fallback detected on FacebookBatchRequest.');
117
            }
118
            $request->setApp($app);
119
        }
120
121
        if (!$request->getAccessToken()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $request->getAccessToken() of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
122
            $accessToken = $this->getAccessToken();
123
            if (!$accessToken) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $accessToken of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
124
                throw new FacebookSDKException('Missing access token on FacebookRequest and no fallback detected on FacebookBatchRequest.');
125
            }
126
            $request->setAccessToken($accessToken);
127
        }
128
    }
129
130
    /**
131
     * Extracts the files from a request.
132
     *
133
     * @param FacebookRequest $request
134
     *
135
     * @return string|null
136
     *
137
     * @throws FacebookSDKException
138
     */
139
    public function extractFileAttachments(FacebookRequest $request)
140
    {
141
        if (!$request->containsFileUploads()) {
142
            return null;
143
        }
144
145
        $files = $request->getFiles();
146
        $fileNames = [];
147
        foreach ($files as $file) {
148
            $fileName = uniqid();
149
            $this->addFile($fileName, $file);
150
            $fileNames[] = $fileName;
151
        }
152
153
        $request->resetFiles();
154
155
        // @TODO Does Graph support multiple uploads on one endpoint?
156
        return implode(',', $fileNames);
157
    }
158
159
    /**
160
     * Return the FacebookRequest entities.
161
     *
162
     * @return array
163
     */
164
    public function getRequests()
165
    {
166
        return $this->requests;
167
    }
168
169
    /**
170
     * Prepares the requests to be sent as a batch request.
171
     *
172
     * @return string
173
     */
174
    public function prepareRequestsForBatch()
175
    {
176
        $this->validateBatchRequestCount();
177
178
        $params = [
179
            'batch' => $this->convertRequestsToJson(),
180
            'include_headers' => true,
181
        ];
182
        $this->setParams($params);
183
    }
184
185
    /**
186
     * Converts the requests into a JSON(P) string.
187
     *
188
     * @return string
189
     */
190
    public function convertRequestsToJson()
191
    {
192
        $requests = [];
193
        foreach ($this->requests as $request) {
194
            $attachedFiles = isset($request['attached_files']) ? $request['attached_files'] : null;
195
            $requests[] = $this->requestEntityToBatchArray($request['request'], $request['name'], $attachedFiles);
196
        }
197
198
        return json_encode($requests);
199
    }
200
201
    /**
202
     * Validate the request count before sending them as a batch.
203
     *
204
     * @throws FacebookSDKException
205
     */
206
    public function validateBatchRequestCount()
207
    {
208
        $batchCount = count($this->requests);
209
        if ($batchCount === 0) {
210
            throw new FacebookSDKException('There are no batch requests to send.');
211
        } elseif ($batchCount > 50) {
212
            // Per: https://developers.facebook.com/docs/graph-api/making-multiple-requests#limits
213
            throw new FacebookSDKException('You cannot send more than 50 batch requests at a time.');
214
        }
215
    }
216
217
    /**
218
     * Converts a Request entity into an array that is batch-friendly.
219
     *
220
     * @param FacebookRequest $request       The request entity to convert.
221
     * @param string|null     $requestName   The name of the request.
222
     * @param string|null     $attachedFiles Names of files associated with the request.
223
     *
224
     * @return array
225
     */
226
    public function requestEntityToBatchArray(FacebookRequest $request, $requestName = null, $attachedFiles = null)
227
    {
228
        $compiledHeaders = [];
229
        $headers = $request->getHeaders();
230
        foreach ($headers as $name => $value) {
231
            $compiledHeaders[] = $name . ': ' . $value;
232
        }
233
234
        $batch = [
235
            'headers' => $compiledHeaders,
236
            'method' => $request->getMethod(),
237
            'relative_url' => $request->getUrl(),
238
        ];
239
240
        // Since file uploads are moved to the root request of a batch request,
241
        // the child requests will always be URL-encoded.
242
        $body = $request->getUrlEncodedBody()->getBody();
243
        if ($body) {
244
            $batch['body'] = $body;
245
        }
246
247
        if (isset($requestName)) {
248
            $batch['name'] = $requestName;
249
        }
250
251
        if (isset($attachedFiles)) {
252
            $batch['attached_files'] = $attachedFiles;
253
        }
254
255
        // @TODO Add support for "omit_response_on_success"
256
        // @TODO Add support for "depends_on"
257
        // @TODO Add support for JSONP with "callback"
258
259
        return $batch;
260
    }
261
262
    /**
263
     * Get an iterator for the items.
264
     *
265
     * @return ArrayIterator
266
     */
267
    public function getIterator()
268
    {
269
        return new ArrayIterator($this->requests);
270
    }
271
272
    /**
273
     * @inheritdoc
274
     */
275
    public function offsetSet($offset, $value)
276
    {
277
        $this->add($value, $offset);
278
    }
279
280
    /**
281
     * @inheritdoc
282
     */
283
    public function offsetExists($offset)
284
    {
285
        return isset($this->requests[$offset]);
286
    }
287
288
    /**
289
     * @inheritdoc
290
     */
291
    public function offsetUnset($offset)
292
    {
293
        unset($this->requests[$offset]);
294
    }
295
296
    /**
297
     * @inheritdoc
298
     */
299
    public function offsetGet($offset)
300
    {
301
        return isset($this->requests[$offset]) ? $this->requests[$offset] : null;
302
    }
303
}
304