validateResponseCastableAsGraphEdge()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Maztech\GraphNodes;
4
5
use Maztech\InstagramResponse;
6
use Maztech\Exceptions\InstagramSDKException;
7
8
/**
9
 * Class GraphNodeFactory
10
 *
11
 * @package Instagram
12
 *
13
 * ## Assumptions ##
14
 * GraphEdge - is ALWAYS a numeric array
15
 * GraphEdge - is ALWAYS an array of GraphNode types
16
 * GraphNode - is ALWAYS an associative array
17
 * GraphNode - MAY contain GraphNode's "recurrable"
18
 * GraphNode - MAY contain GraphEdge's "recurrable"
19
 * GraphNode - MAY contain DateTime's "primitives"
20
 * GraphNode - MAY contain string's "primitives"
21
 */
22
class GraphNodeFactory
23
{
24
    /**
25
     * @const string The base graph object class.
26
     */
27
    const BASE_GRAPH_NODE_CLASS = '\Instagram\GraphNodes\GraphNode';
28
29
    /**
30
     * @const string The base graph edge class.
31
     */
32
    const BASE_GRAPH_EDGE_CLASS = '\Instagram\GraphNodes\GraphEdge';
33
34
    /**
35
     * @const string The graph object prefix.
36
     */
37
    const BASE_GRAPH_OBJECT_PREFIX = '\Instagram\GraphNodes\\';
38
39
    /**
40
     * @var InstagramResponse The response entity from Graph.
41
     */
42
    protected $response;
43
44
    /**
45
     * @var array The decoded body of the InstagramResponse entity from Graph.
46
     */
47
    protected $decodedBody;
48
49
    /**
50
     * Init this Graph object.
51
     *
52
     * @param InstagramResponse $response The response entity from Graph.
53
     */
54
    public function __construct(InstagramResponse $response)
55
    {
56
        $this->response = $response;
57
        $this->decodedBody = $response->getDecodedBody();
58
    }
59
60
    /**
61
     * Tries to convert a InstagramResponse entity into a GraphNode.
62
     *
63
     * @param string|null $subclassName The GraphNode sub class to cast to.
64
     *
65
     * @return GraphNode
66
     *
67
     * @throws InstagramSDKException
68
     */
69
    public function makeGraphNode($subclassName = null)
70
    {
71
        $this->validateResponseAsArray();
72
        $this->validateResponseCastableAsGraphNode();
73
74
        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 Maztech\GraphNodes\GraphEdge which is incompatible with the documented return type Maztech\GraphNodes\GraphNode.
Loading history...
75
    }
76
77
    /**
78
     * Convenience method for creating a GraphAchievement collection.
79
     *
80
     * @return GraphAchievement
0 ignored issues
show
Bug introduced by
The type Maztech\GraphNodes\GraphAchievement was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
81
     *
82
     * @throws InstagramSDKException
83
     */
84
    public function makeGraphAchievement()
85
    {
86
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAchievement');
87
    }
88
89
    /**
90
     * Convenience method for creating a GraphAlbum collection.
91
     *
92
     * @return GraphAlbum
0 ignored issues
show
Bug introduced by
The type Maztech\GraphNodes\GraphAlbum was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
93
     *
94
     * @throws InstagramSDKException
95
     */
96
    public function makeGraphAlbum()
97
    {
98
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphAlbum');
99
    }
100
101
    /**
102
     * Convenience method for creating a GraphPage collection.
103
     *
104
     * @return GraphPage
0 ignored issues
show
Bug introduced by
The type Maztech\GraphNodes\GraphPage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
105
     *
106
     * @throws InstagramSDKException
107
     */
108
    public function makeGraphPage()
109
    {
110
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphPage');
111
    }
112
113
    /**
114
     * Convenience method for creating a GraphSessionInfo collection.
115
     *
116
     * @return GraphSessionInfo
0 ignored issues
show
Bug introduced by
The type Maztech\GraphNodes\GraphSessionInfo was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
117
     *
118
     * @throws InstagramSDKException
119
     */
120
    public function makeGraphSessionInfo()
121
    {
122
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphSessionInfo');
123
    }
124
125
    /**
126
     * Convenience method for creating a GraphUser collection.
127
     *
128
     * @return GraphUser
0 ignored issues
show
Bug introduced by
The type Maztech\GraphNodes\GraphUser was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
129
     *
130
     * @throws InstagramSDKException
131
     */
132
    public function makeGraphUser()
133
    {
134
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphUser');
135
    }
136
137
    /**
138
     * Convenience method for creating a GraphEvent collection.
139
     *
140
     * @return GraphEvent
0 ignored issues
show
Bug introduced by
The type Maztech\GraphNodes\GraphEvent was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
141
     *
142
     * @throws InstagramSDKException
143
     */
144
    public function makeGraphEvent()
145
    {
146
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphEvent');
147
    }
148
149
    /**
150
     * Convenience method for creating a GraphGroup collection.
151
     *
152
     * @return GraphGroup
0 ignored issues
show
Bug introduced by
The type Maztech\GraphNodes\GraphGroup was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
153
     *
154
     * @throws InstagramSDKException
155
     */
156
    public function makeGraphGroup()
157
    {
158
        return $this->makeGraphNode(static::BASE_GRAPH_OBJECT_PREFIX . 'GraphGroup');
159
    }
160
161
    /**
162
     * Tries to convert a InstagramResponse entity into a GraphEdge.
163
     *
164
     * @param string|null $subclassName The GraphNode sub class to cast the list items to.
165
     * @param boolean     $auto_prefix  Toggle to auto-prefix the subclass name.
166
     *
167
     * @return GraphEdge
168
     *
169
     * @throws InstagramSDKException
170
     */
171
    public function makeGraphEdge($subclassName = null, $auto_prefix = true)
172
    {
173
        $this->validateResponseAsArray();
174
        $this->validateResponseCastableAsGraphEdge();
175
176
        if ($subclassName && $auto_prefix) {
177
            $subclassName = static::BASE_GRAPH_OBJECT_PREFIX . $subclassName;
178
        }
179
180
        return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName);
181
    }
182
183
    /**
184
     * Validates the decoded body.
185
     *
186
     * @throws InstagramSDKException
187
     */
188
    public function validateResponseAsArray()
189
    {
190
        if (!is_array($this->decodedBody)) {
0 ignored issues
show
introduced by
The condition is_array($this->decodedBody) is always true.
Loading history...
191
            throw new InstagramSDKException('Unable to get response from Graph as array.', 620);
192
        }
193
    }
194
195
    /**
196
     * Validates that the return data can be cast as a GraphNode.
197
     *
198
     * @throws InstagramSDKException
199
     */
200
    public function validateResponseCastableAsGraphNode()
201
    {
202
        if (isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data'])) {
203
            throw new InstagramSDKException(
204
                'Unable to convert response from Graph to a GraphNode because the response looks like a GraphEdge. Try using GraphNodeFactory::makeGraphEdge() instead.',
205
                620
206
            );
207
        }
208
    }
209
210
    /**
211
     * Validates that the return data can be cast as a GraphEdge.
212
     *
213
     * @throws InstagramSDKException
214
     */
215
    public function validateResponseCastableAsGraphEdge()
216
    {
217
        if (!(isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data']))) {
218
            throw new InstagramSDKException(
219
                'Unable to convert response from Graph to a GraphEdge because the response does not look like a GraphEdge. Try using GraphNodeFactory::makeGraphNode() instead.',
220
                620
221
            );
222
        }
223
    }
224
225
    /**
226
     * Safely instantiates a GraphNode of $subclassName.
227
     *
228
     * @param array       $data         The array of data to iterate over.
229
     * @param string|null $subclassName The subclass to cast this collection to.
230
     *
231
     * @return GraphNode
232
     *
233
     * @throws InstagramSDKException
234
     */
235
    public function safelyMakeGraphNode(array $data, $subclassName = null)
236
    {
237
        $subclassName = $subclassName ?: static::BASE_GRAPH_NODE_CLASS;
238
        static::validateSubclass($subclassName);
239
240
        // Remember the parent node ID
241
        $parentNodeId = isset($data['id']) ? $data['id'] : null;
242
243
        $items = [];
244
245
        foreach ($data as $k => $v) {
246
            // Array means could be recurable
247
            if (is_array($v)) {
248
                // Detect any smart-casting from the $graphObjectMap array.
249
                // This is always empty on the GraphNode collection, but subclasses can define
250
                // their own array of smart-casting types.
251
                $graphObjectMap = $subclassName::getObjectMap();
252
                $objectSubClass = isset($graphObjectMap[$k])
253
                    ? $graphObjectMap[$k]
254
                    : null;
255
256
                // Could be a GraphEdge or GraphNode
257
                $items[$k] = $this->castAsGraphNodeOrGraphEdge($v, $objectSubClass, $k, $parentNodeId);
258
            } else {
259
                $items[$k] = $v;
260
            }
261
        }
262
263
        return new $subclassName($items);
264
    }
265
266
    /**
267
     * Takes an array of values and determines how to cast each node.
268
     *
269
     * @param array       $data         The array of data to iterate over.
270
     * @param string|null $subclassName The subclass to cast this collection to.
271
     * @param string|null $parentKey    The key of this data (Graph edge).
272
     * @param string|null $parentNodeId The parent Graph node ID.
273
     *
274
     * @return GraphNode|GraphEdge
275
     *
276
     * @throws InstagramSDKException
277
     */
278
    public function castAsGraphNodeOrGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null)
279
    {
280
        if (isset($data['data'])) {
281
            // Create GraphEdge
282
            if (static::isCastableAsGraphEdge($data['data'])) {
283
                return $this->safelyMakeGraphEdge($data, $subclassName, $parentKey, $parentNodeId);
284
            }
285
            // Sometimes Graph is a weirdo and returns a GraphNode under the "data" key
286
            $outerData = $data;
287
            unset($outerData['data']);
288
            $data = $data['data'] + $outerData;
289
        }
290
291
        // Create GraphNode
292
        return $this->safelyMakeGraphNode($data, $subclassName);
293
    }
294
295
    /**
296
     * Return an array of GraphNode's.
297
     *
298
     * @param array       $data         The array of data to iterate over.
299
     * @param string|null $subclassName The GraphNode subclass to cast each item in the list to.
300
     * @param string|null $parentKey    The key of this data (Graph edge).
301
     * @param string|null $parentNodeId The parent Graph node ID.
302
     *
303
     * @return GraphEdge
304
     *
305
     * @throws InstagramSDKException
306
     */
307
    public function safelyMakeGraphEdge(array $data, $subclassName = null, $parentKey = null, $parentNodeId = null)
308
    {
309
        if (!isset($data['data'])) {
310
            throw new InstagramSDKException('Cannot cast data to GraphEdge. Expected a "data" key.', 620);
311
        }
312
313
        $dataList = [];
314
        foreach ($data['data'] as $graphNode) {
315
            $dataList[] = $this->safelyMakeGraphNode($graphNode, $subclassName);
316
        }
317
318
        $metaData = $this->getMetaData($data);
319
320
        // We'll need to make an edge endpoint for this in case it's a GraphEdge (for cursor pagination)
321
        $parentGraphEdgeEndpoint = $parentNodeId && $parentKey ? '/' . $parentNodeId . '/' . $parentKey : null;
322
        $className = static::BASE_GRAPH_EDGE_CLASS;
323
324
        return new $className($this->response->getRequest(), $dataList, $metaData, $parentGraphEdgeEndpoint, $subclassName);
325
    }
326
327
    /**
328
     * Get the meta data from a list in a Graph response.
329
     *
330
     * @param array $data The Graph response.
331
     *
332
     * @return array
333
     */
334
    public function getMetaData(array $data)
335
    {
336
        unset($data['data']);
337
338
        return $data;
339
    }
340
341
    /**
342
     * Determines whether or not the data should be cast as a GraphEdge.
343
     *
344
     * @param array $data
345
     *
346
     * @return boolean
347
     */
348
    public static function isCastableAsGraphEdge(array $data)
349
    {
350
        if ($data === []) {
351
            return true;
352
        }
353
354
        // Checks for a sequential numeric array which would be a GraphEdge
355
        return array_keys($data) === range(0, count($data) - 1);
356
    }
357
358
    /**
359
     * Ensures that the subclass in question is valid.
360
     *
361
     * @param string $subclassName The GraphNode subclass to validate.
362
     *
363
     * @throws InstagramSDKException
364
     */
365
    public static function validateSubclass($subclassName)
366
    {
367
        if ($subclassName == static::BASE_GRAPH_NODE_CLASS || is_subclass_of($subclassName, static::BASE_GRAPH_NODE_CLASS)) {
368
            return;
369
        }
370
371
        throw new InstagramSDKException('The given subclass "' . $subclassName . '" is not valid. Cannot cast to an object that is not a GraphNode subclass.', 620);
372
    }
373
}
374