• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

janu-software / facebook-php-sdk / 13285164661

12 Feb 2025 12:17PM UTC coverage: 93.359%. Remained the same
13285164661

push

github

stanislav-janu
php 8.1 lock file

956 of 1024 relevant lines covered (93.36%)

11.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.74
/src/GraphNode/GraphNodeFactory.php
1
<?php
2

3
declare(strict_types=1);
4
/**
5
 * Copyright 2017 Facebook, Inc.
6
 * You are hereby granted a non-exclusive, worldwide, royalty-free license to
7
 * use, copy, modify, and distribute this software in source code or binary
8
 * form for use in connection with the web services and APIs provided by
9
 * Facebook.
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
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20
 * DEALINGS IN THE SOFTWARE.
21
 */
22

23
namespace JanuSoftware\Facebook\GraphNode;
24

25
use JanuSoftware\Facebook\Exception\SDKException;
26
use JanuSoftware\Facebook\Response;
27

28

29
/**
30
 * Class GraphNodeFactory.
31
 * ## Assumptions ##
32
 * GraphEdge - is ALWAYS a numeric array
33
 * GraphEdge - is ALWAYS an array of GraphNode types
34
 * GraphNode - is ALWAYS an associative array
35
 * GraphNode - MAY contain GraphNode's "recurrable"
36
 * GraphNode - MAY contain GraphEdge's "recurrable"
37
 * GraphNode - MAY contain DateTime's "primitives"
38
 * GraphNode - MAY contain string's "primitives"
39
 */
40
class GraphNodeFactory
41
{
42
        /**
43
         * @const string The base graph object class.
44
         */
45
        final public const BaseGraphNodeClass = GraphNode::class;
46

47
        /**
48
         * @const string The base graph edge class.
49
         */
50
        final public const BaseGraphEdgeClass = GraphEdge::class;
51

52
        /**
53
         * @const string The graph object prefix.
54
         */
55
        final public const BaseGraphObjectPrefix = '\JanuSoftware\Facebook\GraphNode\\';
56

57
        /**
58
         * The decoded body of the Response entity from Graph
59
         */
60
        protected array $decodedBody;
61

62

63
        /**
64
         * Init this Graph object.
65
         *
66
         * @param Response $response the response entity from Graph
67
         */
68
        public function __construct(
69
                protected Response $response,
70
        ) {
71
                $this->decodedBody = $response->getDecodedBody();
16✔
72
        }
73

74

75
        /**
76
         * Tries to convert a Response entity into a GraphNode.
77
         *
78
         * @param string|null $subclassName the GraphNode sub class to cast to
79
         *
80
         * @throws SDKException
81
         */
82
        public function makeGraphNode(
83
                ?string $subclassName = null,
84
        ): GraphNode {
85
                $this->validateResponseCastableAsGraphNode();
7✔
86

87
                return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName);
7✔
88
        }
89

90

91
        /**
92
         * Tries to convert a Response entity into a GraphEdge.
93
         *
94
         * @param string|null $subclassName the GraphNode sub class to cast the list items to
95
         * @param bool        $auto_prefix  toggle to auto-prefix the subclass name
96
         *
97
         * @throws SDKException
98
         */
99
        public function makeGraphEdge(?string $subclassName = null, bool $auto_prefix = true): GraphEdge
100
        {
101
                $this->validateResponseCastableAsGraphEdge();
6✔
102

103
                if ($subclassName !== null && $auto_prefix) {
6✔
104
                        $subclassName = static::BaseGraphObjectPrefix . $subclassName;
×
105
                }
106

107
                return $this->castAsGraphNodeOrGraphEdge($this->decodedBody, $subclassName);
6✔
108
        }
109

110

111
        /**
112
         * Validates that the return data can be cast as a GraphNode.
113
         * @throws SDKException
114
         */
115
        public function validateResponseCastableAsGraphNode(): void
116
        {
117
                if (isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data'])) {
9✔
118
                        throw new SDKException('Unable to convert response from Graph to a GraphNode because the response looks like a GraphEdge. Try using GraphNodeFactory::makeGraphEdge() instead.', 620);
1✔
119
                }
120
        }
121

122

123
        /**
124
         * Validates that the return data can be cast as a GraphEdge.
125
         * @throws SDKException
126
         */
127
        public function validateResponseCastableAsGraphEdge(): void
128
        {
129
                if (!(isset($this->decodedBody['data']) && static::isCastableAsGraphEdge($this->decodedBody['data']))) {
8✔
130
                        throw new SDKException('Unable to convert response from Graph to a GraphEdge because the response does not look like a GraphEdge. Try using GraphNodeFactory::makeGraphNode() instead.', 620);
1✔
131
                }
132
        }
133

134

135
        /**
136
         * Safely instantiates a GraphNode of $subclassName.
137
         *
138
         * @param array       $data         the array of data to iterate over
139
         * @param string|null $subclassName the subclass to cast this collection to
140
         *
141
         * @throws SDKException
142
         */
143
        public function safelyMakeGraphNode(array $data, ?string $subclassName = null): GraphNode
144
        {
145
                $subclassName ??= static::BaseGraphNodeClass;
12✔
146
                static::validateSubclass($subclassName);
12✔
147

148
                // Remember the parent node ID
149
                $parentNodeId = $data['id'] ?? null;
12✔
150

151
                $items = [];
12✔
152

153
                foreach ($data as $k => $v) {
12✔
154
                        // Array means could be recurable
155
                        if (is_array($v)) {
12✔
156
                                // Detect any smart-casting from the $graphNodeMap array.
157
                                // This is always empty on the GraphNode collection, but subclasses can define
158
                                // their own array of smart-casting types.
159
                                $graphNodeMap = $subclassName::getNodeMap();
4✔
160
                                $objectSubClass = $graphNodeMap[$k] ?? null;
4✔
161

162
                                // Could be a GraphEdge or GraphNode
163
                                $items[$k] = $this->castAsGraphNodeOrGraphEdge($v, $objectSubClass, $k, $parentNodeId);
4✔
164
                        } else {
165
                                $items[$k] = $v;
12✔
166
                        }
167
                }
168

169
                return new $subclassName($items);
12✔
170
        }
171

172

173
        /**
174
         * Takes an array of values and determines how to cast each node.
175
         *
176
         * @param array           $data         the array of data to iterate over
177
         * @param string|null     $subclassName the subclass to cast this collection to
178
         * @param string|int|null $parentKey    the key of this data (Graph edge)
179
         * @param string|null     $parentNodeId the parent Graph node ID
180
         *
181
         * @throws SDKException
182
         */
183
        public function castAsGraphNodeOrGraphEdge(
184
                array $data,
185
                ?string $subclassName = null,
186
                string|int|null $parentKey = null,
187
                ?string $parentNodeId = null,
188
        ): GraphEdge|GraphNode {
189
                if (isset($data['data'])) {
12✔
190
                        // Create GraphEdge
191
                        if (static::isCastableAsGraphEdge($data['data'])) {
7✔
192
                                return $this->safelyMakeGraphEdge($data, $subclassName, $parentKey, $parentNodeId);
6✔
193
                        }
194
                        // Sometimes Graph is a weirdo and returns a GraphNode under the "data" key
195
                        $data = $data['data'];
1✔
196
                }
197

198
                // Create GraphNode
199
                return $this->safelyMakeGraphNode($data, $subclassName);
8✔
200
        }
201

202

203
        /**
204
         * Return an array of GraphNode's.
205
         *
206
         * @param array       $data         the array of data to iterate over
207
         * @param string|null $subclassName the GraphNode subclass to cast each item in the list to
208
         * @param string|int|null $parentKey the key of this data (Graph edge)
209
         * @param string|null $parentNodeId the parent Graph node ID
210
         *
211
         * @throws SDKException
212
         */
213
        public function safelyMakeGraphEdge(
214
                array $data,
215
                ?string $subclassName = null,
216
                string|int|null $parentKey = null,
217
                ?string $parentNodeId = null,
218
        ): GraphEdge {
219
                if (!isset($data['data'])) {
6✔
220
                        throw new SDKException('Cannot cast data to GraphEdge. Expected a "data" key.', 620);
×
221
                }
222

223
                $dataList = [];
6✔
224
                foreach ($data['data'] as $graphNode) {
6✔
225
                        $dataList[] = $this->safelyMakeGraphNode($graphNode, $subclassName);
6✔
226
                }
227

228
                $metaData = $this->getMetaData($data);
6✔
229

230
                // We'll need to make an edge endpoint for this in case it's a GraphEdge (for cursor pagination)
231
                $parentGraphEdgeEndpoint = $parentNodeId !== null && $parentKey !== null
6✔
232
                        ? '/' . $parentNodeId . '/' . $parentKey
2✔
233
                        : null;
6✔
234
                $className = static::BaseGraphEdgeClass;
6✔
235

236
                return new $className($this->response->getRequest(), $dataList, $metaData, $parentGraphEdgeEndpoint, $subclassName);
6✔
237
        }
238

239

240
        /**
241
         * Get the meta data from a list in a Graph response.
242
         *
243
         * @param array $data the Graph response
244
         * @return mixed[]
245
         */
246
        public function getMetaData(array $data): array
247
        {
248
                unset($data['data']);
6✔
249

250
                return $data;
6✔
251
        }
252

253

254
        /**
255
         * Determines whether or not the data should be cast as a GraphEdge.
256
         */
257
        public static function isCastableAsGraphEdge(array $data): bool
258
        {
259
                if ($data === []) {
10✔
260
                        return true;
1✔
261
                }
262

263
                // Checks for a sequential numeric array which would be a GraphEdge
264
                return array_keys($data) === range(0, count($data) - 1);
10✔
265
        }
266

267

268
        /**
269
         * Ensures that the subclass in question is valid.
270
         *
271
         * @param string $subclassName the GraphNode subclass to validate
272
         *
273
         * @throws SDKException
274
         */
275
        public static function validateSubclass(string $subclassName): void
276
        {
277
                if (
278
                        $subclassName === static::BaseGraphNodeClass
14✔
279
                        || is_subclass_of($subclassName, static::BaseGraphNodeClass)
14✔
280
                ) {
281
                        return;
13✔
282
                }
283

284
                throw new SDKException('The given subclass "' . $subclassName . '" is not valid. Cannot cast to an object that is not a GraphNode subclass.', 620);
1✔
285
        }
286
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc