Completed
Push — master ( 472099...db9c1f )
by Yassine
15s
created

FacebookBatchRequest   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 284
Duplicated Lines 3.52 %

Importance

Changes 0
Metric Value
dl 10
loc 284
rs 8.8
c 0
b 0
f 0
wmc 36

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A offsetExists() 0 3 1
A extractFileAttachments() 0 18 3
A getRequests() 0 3 1
A validateBatchRequestCount() 0 8 3
A convertRequestsToJson() 0 16 3
B requestEntityToBatchArray() 5 35 6
A offsetSet() 0 3 1
A getIterator() 0 3 1
A prepareRequestsForBatch() 0 9 1
B addFallbackDefaults() 0 16 5
A offsetGet() 0 3 2
A offsetUnset() 0 3 1
C add() 5 39 7

How to fix   Duplicated Code   

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:

1
<?php
2
/**
3
 * Copyright 2017 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\Exception\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
     * Adds a new request to the array.
66
     *
67
     * @param FacebookRequest|array $request
68
     * @param string|null|array     $options Array of batch request options e.g. 'name', 'omit_response_on_success'.
69
     *                                       If a string is given, it is the value of the 'name' option.
70
     *
71
     * @return FacebookBatchRequest
72
     *
73
     * @throws \InvalidArgumentException
74
     */
75
    public function add($request, $options = null)
76
    {
77
        if (is_array($request)) {
78
            foreach ($request as $key => $req) {
79
                $this->add($req, $key);
80
            }
81
82
            return $this;
83
        }
84
85
        if (!$request instanceof FacebookRequest) {
86
            throw new \InvalidArgumentException('Argument for add() must be of type array or FacebookRequest.');
87
        }
88
89 View Code Duplication
        if (null === $options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
90
            $options = [];
91
        } elseif (!is_array($options)) {
92
            $options = ['name' => $options];
93
        }
94
95
        $this->addFallbackDefaults($request);
96
97
        // File uploads
98
        $attachedFiles = $this->extractFileAttachments($request);
99
100
        $name = isset($options['name']) ? $options['name'] : null;
101
102
        unset($options['name']);
103
104
        $requestToAdd = [
105
            'name' => $name,
106
            'request' => $request,
107
            'options' => $options,
108
            'attached_files' => $attachedFiles,
109
        ];
110
111
        $this->requests[] = $requestToAdd;
112
113
        return $this;
114
    }
115
116
    /**
117
     * Ensures that the FacebookApp and access token fall back when missing.
118
     *
119
     * @param FacebookRequest $request
120
     *
121
     * @throws FacebookSDKException
122
     */
123
    public function addFallbackDefaults(FacebookRequest $request)
124
    {
125
        if (!$request->getApp()) {
126
            $app = $this->getApp();
127
            if (!$app) {
128
                throw new FacebookSDKException('Missing FacebookApp on FacebookRequest and no fallback detected on FacebookBatchRequest.');
129
            }
130
            $request->setApp($app);
131
        }
132
133
        if (!$request->getAccessToken()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $request->getAccessToken() of type null|string 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...
134
            $accessToken = $this->getAccessToken();
135
            if (!$accessToken) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $accessToken of type null|string 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...
136
                throw new FacebookSDKException('Missing access token on FacebookRequest and no fallback detected on FacebookBatchRequest.');
137
            }
138
            $request->setAccessToken($accessToken);
139
        }
140
    }
141
142
    /**
143
     * Extracts the files from a request.
144
     *
145
     * @param FacebookRequest $request
146
     *
147
     * @return string|null
148
     *
149
     * @throws FacebookSDKException
150
     */
151
    public function extractFileAttachments(FacebookRequest $request)
152
    {
153
        if (!$request->containsFileUploads()) {
154
            return null;
155
        }
156
157
        $files = $request->getFiles();
158
        $fileNames = [];
159
        foreach ($files as $file) {
160
            $fileName = uniqid();
161
            $this->addFile($fileName, $file);
162
            $fileNames[] = $fileName;
163
        }
164
165
        $request->resetFiles();
166
167
        // @TODO Does Graph support multiple uploads on one endpoint?
168
        return implode(',', $fileNames);
169
    }
170
171
    /**
172
     * Return the FacebookRequest entities.
173
     *
174
     * @return array
175
     */
176
    public function getRequests()
177
    {
178
        return $this->requests;
179
    }
180
181
    /**
182
     * Prepares the requests to be sent as a batch request.
183
     */
184
    public function prepareRequestsForBatch()
185
    {
186
        $this->validateBatchRequestCount();
187
188
        $params = [
189
            'batch' => $this->convertRequestsToJson(),
190
            'include_headers' => true,
191
        ];
192
        $this->setParams($params);
193
    }
194
195
    /**
196
     * Converts the requests into a JSON(P) string.
197
     *
198
     * @return string
199
     */
200
    public function convertRequestsToJson()
201
    {
202
        $requests = [];
203
        foreach ($this->requests as $request) {
204
            $options = [];
205
206
            if (null !== $request['name']) {
207
                $options['name'] = $request['name'];
208
            }
209
210
            $options += $request['options'];
211
212
            $requests[] = $this->requestEntityToBatchArray($request['request'], $options, $request['attached_files']);
213
        }
214
215
        return json_encode($requests);
216
    }
217
218
    /**
219
     * Validate the request count before sending them as a batch.
220
     *
221
     * @throws FacebookSDKException
222
     */
223
    public function validateBatchRequestCount()
224
    {
225
        $batchCount = count($this->requests);
226
        if ($batchCount === 0) {
227
            throw new FacebookSDKException('There are no batch requests to send.');
228
        } elseif ($batchCount > 50) {
229
            // Per: https://developers.facebook.com/docs/graph-api/making-multiple-requests#limits
230
            throw new FacebookSDKException('You cannot send more than 50 batch requests at a time.');
231
        }
232
    }
233
234
    /**
235
     * Converts a Request entity into an array that is batch-friendly.
236
     *
237
     * @param FacebookRequest   $request       The request entity to convert.
238
     * @param string|null|array $options       Array of batch request options e.g. 'name', 'omit_response_on_success'.
239
     *                                         If a string is given, it is the value of the 'name' option.
240
     * @param string|null       $attachedFiles Names of files associated with the request.
241
     *
242
     * @return array
243
     */
244
    public function requestEntityToBatchArray(FacebookRequest $request, $options = null, $attachedFiles = null)
245
    {
246
247 View Code Duplication
        if (null === $options) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
248
            $options = [];
249
        } elseif (!is_array($options)) {
250
            $options = ['name' => $options];
251
        }
252
253
        $compiledHeaders = [];
254
        $headers = $request->getHeaders();
255
        foreach ($headers as $name => $value) {
256
            $compiledHeaders[] = $name . ': ' . $value;
257
        }
258
259
        $batch = [
260
            'headers' => $compiledHeaders,
261
            'method' => $request->getMethod(),
262
            'relative_url' => $request->getUrl(),
263
        ];
264
265
        // Since file uploads are moved to the root request of a batch request,
266
        // the child requests will always be URL-encoded.
267
        $body = $request->getUrlEncodedBody()->getBody();
268
        if ($body) {
269
            $batch['body'] = $body;
270
        }
271
272
        $batch += $options;
273
274
        if (null !== $attachedFiles) {
275
            $batch['attached_files'] = $attachedFiles;
276
        }
277
278
        return $batch;
279
    }
280
281
    /**
282
     * Get an iterator for the items.
283
     *
284
     * @return ArrayIterator
285
     */
286
    public function getIterator()
287
    {
288
        return new ArrayIterator($this->requests);
289
    }
290
291
    /**
292
     * @inheritdoc
293
     */
294
    public function offsetSet($offset, $value)
295
    {
296
        $this->add($value, $offset);
297
    }
298
299
    /**
300
     * @inheritdoc
301
     */
302
    public function offsetExists($offset)
303
    {
304
        return isset($this->requests[$offset]);
305
    }
306
307
    /**
308
     * @inheritdoc
309
     */
310
    public function offsetUnset($offset)
311
    {
312
        unset($this->requests[$offset]);
313
    }
314
315
    /**
316
     * @inheritdoc
317
     */
318
    public function offsetGet($offset)
319
    {
320
        return isset($this->requests[$offset]) ? $this->requests[$offset] : null;
321
    }
322
}
323