Completed
Pull Request — master (#1085)
by
unknown
02:42 queued 01:03
created

GraphNodeFactory   A

Complexity

Total Complexity 39

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Test Coverage

Coverage 96.43%

Importance

Changes 0
Metric Value
eloc 68
dl 0
loc 349
ccs 81
cts 84
cp 0.9643
rs 9.28
c 0
b 0
f 0
wmc 39

19 Methods

Rating   Name   Duplication   Size   Complexity  
A makeGraphSessionInfo() 0 3 1
A makeGraphAlbum() 0 3 1
A makeGraphEvent() 0 3 1
A makeGraphUser() 0 3 1
A makeGraphPage() 0 3 1
A makeGraphGroup() 0 3 1
A __construct() 0 4 1
A validateResponseAsArray() 0 4 2
A makeGraphAchievement() 0 3 1
A makeGraphNode() 0 6 1
A validateResponseCastableAsGraphEdge() 0 6 3
A makeGraphEdge() 0 10 3
A validateResponseCastableAsGraphNode() 0 6 3
A safelyMakeGraphNode() 0 27 4
A safelyMakeGraphEdge() 0 18 5
A validateSubclass() 0 7 3
A isCastableAsGraphEdge() 0 8 2
A getMetaData() 0 5 1
A castAsGraphNodeOrGraphEdge() 0 16 4
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\GraphNode;
24
25
use Facebook\Response;
26
use Facebook\Exception\SDKException;
27
28
/**
29
 * Class GraphNodeFactory.
30
 *
31
 * @package Facebook
32
 *
33
 * ## Assumptions ##
34
 * GraphEdge - is ALWAYS a numeric array
35
 * GraphEdge - is ALWAYS an array of GraphNode types
36
 * GraphNode - is ALWAYS an associative array
37
 * GraphNode - MAY contain GraphNode's "recurrable"
38
 * GraphNode - MAY contain GraphEdge's "recurrable"
39
 * GraphNode - MAY contain DateTime's "primitives"
40
 * GraphNode - MAY contain string's "primitives"
41
 */
42
class GraphNodeFactory
43
{
44
    /**
45
     * @const string The base graph object class.
46
     */
47
    const BASE_GRAPH_NODE_CLASS = GraphNode::class;
48
49
    /**
50
     * @const string The base graph edge class.
51
     */
52
    const BASE_GRAPH_EDGE_CLASS = GraphEdge::class;
53
54
    /**
55
     * @const string The graph object prefix.
56
     */
57
    const BASE_GRAPH_OBJECT_PREFIX = '\Facebook\GraphNode\\';
58
59
    /**
60
     * @var Response the response entity from Graph
61
     */
62
    protected $response;
63
64
    /**
65
     * @var array the decoded body of the Response entity from Graph
66
     */
67
    protected $decodedBody;
68
69
    /**
70
     * Init this Graph object.
71
     *
72
     * @param Response $response the response entity from Graph
73
     */
74 42
    public function __construct(Response $response)
75
    {
76 42
        $this->response = $response;
77 42
        $this->decodedBody = $response->getDecodedBody();
78 42
    }
79
80
    /**
81
     * Tries to convert a Response entity into a GraphNode.
82
     *
83
     * @param null|string $subclassName the GraphNode sub class to cast to
84
     *
85
     * @throws SDKException
86
     *
87
     * @return GraphNode
88
     */
89 33
    public function makeGraphNode($subclassName = null)
90
    {
91 33
        $this->validateResponseAsArray();
92 33
        $this->validateResponseCastableAsGraphNode();
93
94 33
        return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->castAsGrap...dedBody, $subclassName) also could return the type Facebook\GraphNode\GraphEdge which is incompatible with the documented return type Facebook\GraphNode\GraphNode.
Loading history...
95
    }
96
97
    /**
98
     * Convenience method for creating a GraphAchievement collection.
99
     *
100
     * @throws SDKException
101
     *
102
     * @return GraphAchievement
103
     */
104 6
    public function makeGraphAchievement()
105
    {
106 6
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAchievement');
107
    }
108
109
    /**
110
     * Convenience method for creating a GraphAlbum collection.
111
     *
112
     * @throws SDKException
113
     *
114
     * @return GraphAlbum
115
     */
116 3
    public function makeGraphAlbum()
117
    {
118 3
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAlbum');
119
    }
120
121
    /**
122
     * Convenience method for creating a GraphPage collection.
123
     *
124
     * @throws SDKException
125
     *
126
     * @return GraphPage
127
     */
128 2
    public function makeGraphPage()
129
    {
130 2
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphPage');
131
    }
132
133
    /**
134
     * Convenience method for creating a GraphSessionInfo collection.
135
     *
136
     * @throws SDKException
137
     *
138
     * @return GraphSessionInfo
139
     */
140 1
    public function makeGraphSessionInfo()
141
    {
142 1
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphSessionInfo');
143
    }
144
145
    /**
146
     * Convenience method for creating a GraphUser collection.
147
     *
148
     * @throws SDKException
149
     *
150
     * @return GraphUser
151
     */
152 7
    public function makeGraphUser()
153
    {
154 7
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphUser');
155
    }
156
157
    /**
158
     * Convenience method for creating a GraphEvent collection.
159
     *
160
     * @throws SDKException
161
     *
162
     * @return GraphEvent
163
     */
164 4
    public function makeGraphEvent()
165
    {
166 4
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphEvent');
167
    }
168
169
    /**
170
     * Convenience method for creating a GraphGroup collection.
171
     *
172
     * @throws SDKException
173
     *
174
     * @return GraphGroup
175
     */
176 2
    public function makeGraphGroup()
177
    {
178 2
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphGroup');
179
    }
180
181
    /**
182
     * Tries to convert a Response entity into a GraphEdge.
183
     *
184
     * @param null|string $subclassName the GraphNode sub class to cast the list items to
185
     * @param bool        $auto_prefix  toggle to auto-prefix the subclass name
186
     *
187
     * @throws SDKException
188
     *
189
     * @return GraphEdge
190
     */
191 6
    public function makeGraphEdge($subclassName = null, $auto_prefix = true)
192
    {
193 6
        $this->validateResponseAsArray();
194 6
        $this->validateResponseCastableAsGraphEdge();
195
196 6
        if ($subclassName && $auto_prefix) {
197
            $subclassName = static::BASE_GRAPH_OBJECT_PREFIX . $subclassName;
198
        }
199
200 6
        return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->castAsGrap...dedBody, $subclassName) also could return the type Facebook\GraphNode\GraphNode which is incompatible with the documented return type Facebook\GraphNode\GraphEdge.
Loading history...
201
    }
202
203
    /**
204
     * Validates the decoded body.
205
     *
206
     * @throws SDKException
207
     */
208 38
    public function validateResponseAsArray()
209
    {
210 38
        if (!is_array($this->decodedBody)) {
0 ignored issues
show
introduced by
The condition is_array($this->decodedBody) is always true.
Loading history...
211
            throw new SDKException('Unable to get response from Graph as array.', 620);
212
        }
213 38
    }
214
215
    /**
216
     * Validates that the return data can be cast as a GraphNode.
217
     *
218
     * @throws SDKException
219
     */
220 35
    public function validateResponseCastableAsGraphNode()
221
    {
222 35
        if (isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data'])) {
223 1
            throw new SDKException(
224 1
                'Unable to convert response from Graph to a GraphNode because the response looks like a GraphEdge. Try using GraphNodeFactory::makeGraphEdge() instead.',
225 1
                620
226
            );
227
        }
228 34
    }
229
230
    /**
231
     * Validates that the return data can be cast as a GraphEdge.
232
     *
233
     * @throws SDKException
234
     */
235 8
    public function validateResponseCastableAsGraphEdge()
236
    {
237 8
        if (!(isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data']))) {
238 1
            throw new SDKException(
239 1
                'Unable to convert response from Graph to a GraphEdge because the response does not look like a GraphEdge. Try using GraphNodeFactory::makeGraphNode() instead.',
240 1
                620
241
            );
242
        }
243 7
    }
244
245
    /**
246
     * Safely instantiates a GraphNode of $subclassName.
247
     *
248
     * @param array       $data         the array of data to iterate over
249
     * @param null|string $subclassName the subclass to cast this collection to
250
     *
251
     * @throws SDKException
252
     *
253
     * @return GraphNode
254
     */
255 38
    public function safelyMakeGraphNode(array $data, $subclassName = null)
256
    {
257 38
        $subclassName = $subclassName ?: static::BASE_GRAPH_NODE_CLASS;
258 38
        static::validateSubclass($subclassName);
259
260
        // Remember the parent node ID
261 38
        $parentNodeId = $data['id'] ?? null;
262
263 38
        $items = [];
264
265 38
        foreach ($data as $k => $v) {
266
            // Array means could be recurable
267 38
            if (is_array($v)) {
268
                // Detect any smart-casting from the $graphNodeMap array.
269
                // This is always empty on the GraphNode collection, but subclasses can define
270
                // their own array of smart-casting types.
271 20
                $graphNodeMap = $subclassName::getNodeMap();
272 20
                $objectSubClass = $graphNodeMap[$k] ?? null;
273
274
                // Could be a GraphEdge or GraphNode
275 20
                $items[$k] = $this->castAsGraphNodeOrGraphEdge($v, $objectSubClass, $k, $parentNodeId);
276
            } else {
277 38
                $items[$k] = $v;
278
            }
279
        }
280
281 38
        return new $subclassName($items);
282
    }
283
284
    /**
285
     * Takes an array of values and determines how to cast each node.
286
     *
287
     * @param array       $data         the array of data to iterate over
288
     * @param null|string $subclassName the subclass to cast this collection to
289
     * @param null|string $parentKey    the key of this data (Graph edge)
290
     * @param null|string $parentNodeId the parent Graph node ID
291
     *
292
     * @throws SDKException
293
     *
294
     * @return GraphEdge|GraphNode
295
     */
296 38
    public function castAsGraphNodeOrGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null)
297
    {
298 38
        if (isset($data['data'])) {
299
            // Create GraphEdge
300 8
            if (static::isCastableAsGraphEdge($data['data'])) {
301 6
                return $this->safelyMakeGraphEdge($data, $subclassName, $parentKey, $parentNodeId);
302
            }
303
304
            // Sometimes Graph is a weirdo and returns a GraphNode under the "data" key
305 2
            if (count($data) === 1) {
306 1
                $data = $data['data'];
307
            }
308
        }
309
310
        // Create GraphNode
311 34
        return $this->safelyMakeGraphNode($data, $subclassName);
312
    }
313
314
    /**
315
     * Return an array of GraphNode's.
316
     *
317
     * @param array       $data         the array of data to iterate over
318
     * @param null|string $subclassName the GraphNode subclass to cast each item in the list to
319
     * @param null|string $parentKey    the key of this data (Graph edge)
320
     * @param null|string $parentNodeId the parent Graph node ID
321
     *
322
     * @throws SDKException
323
     *
324
     * @return GraphEdge
325
     */
326 6
    public function safelyMakeGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null)
327
    {
328 6
        if (!isset($data['data'])) {
329
            throw new SDKException('Cannot cast data to GraphEdge. Expected a "data" key.', 620);
330
        }
331
332 6
        $dataList = [];
333 6
        foreach ($data['data'] as $graphNode) {
334 6
            $dataList[] = $this->safelyMakeGraphNode($graphNode, $subclassName);
335
        }
336
337 6
        $metaData = $this->getMetaData($data);
338
339
        // We'll need to make an edge endpoint for this in case it's a GraphEdge (for cursor pagination)
340 6
        $parentGraphEdgeEndpoint = $parentNodeId && $parentKey ? '/' . $parentNodeId . '/' . $parentKey : null;
341 6
        $className = static::BASE_GRAPH_EDGE_CLASS;
342
343 6
        return new $className($this->response->getRequest(), $dataList, $metaData, $parentGraphEdgeEndpoint, $subclassName);
344
    }
345
346
    /**
347
     * Get the meta data from a list in a Graph response.
348
     *
349
     * @param array $data the Graph response
350
     *
351
     * @return array
352
     */
353 6
    public function getMetaData(array $data)
354
    {
355 6
        unset($data['data']);
356
357 6
        return $data;
358
    }
359
360
    /**
361
     * Determines whether or not the data should be cast as a GraphEdge.
362
     *
363
     * @param array $data
364
     *
365
     * @return bool
366
     */
367 11
    public static function isCastableAsGraphEdge(array $data)
368
    {
369 11
        if ($data === []) {
370 1
            return true;
371
        }
372
373
        // Checks for a sequential numeric array which would be a GraphEdge
374 11
        return array_keys($data) === range(0, count($data) - 1);
375
    }
376
377
    /**
378
     * Ensures that the subclass in question is valid.
379
     *
380
     * @param string $subclassName the GraphNode subclass to validate
381
     *
382
     * @throws SDKException
383
     */
384 40
    public static function validateSubclass($subclassName)
385
    {
386 40
        if ($subclassName == static::BASE_GRAPH_NODE_CLASS || is_subclass_of($subclassName, static::BASE_GRAPH_NODE_CLASS)) {
387 39
            return;
388
        }
389
390 1
        throw new SDKException('The given subclass "' . $subclassName . '" is not valid. Cannot cast to an object that is not a GraphNode subclass.', 620);
391
    }
392
}
393