GraphNodeFactory   A
last analyzed

Complexity

Total Complexity 40

Size/Duplication

Total Lines 350
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 71
c 1
b 0
f 1
dl 0
loc 350
rs 9.2
wmc 40

19 Methods

Rating   Name   Duplication   Size   Complexity  
A makeGraphGroup() 0 3 1
A makeGraphEvent() 0 3 1
A __construct() 0 4 1
A makeGraphEdge() 0 10 3
A safelyMakeGraphNode() 0 29 6
A validateSubclass() 0 7 3
A makeGraphPage() 0 3 1
A makeGraphNode() 0 6 1
A isCastableAsGraphEdge() 0 8 2
A validateResponseCastableAsGraphEdge() 0 6 3
A getMetaData() 0 5 1
A safelyMakeGraphEdge() 0 18 5
A makeGraphAlbum() 0 3 1
A validateResponseAsArray() 0 4 2
A makeGraphSessionInfo() 0 3 1
A validateResponseCastableAsGraphNode() 0 6 3
A makeGraphAchievement() 0 3 1
A castAsGraphNodeOrGraphEdge() 0 15 3
A makeGraphUser() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like GraphNodeFactory often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use GraphNodeFactory, and based on these observations, apply Extract Interface, too.

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