Issues (25)

src/BatchRequest.php (1 issue)

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