Completed
Pull Request — master (#1148)
by
unknown
02:05
created

GraphNode::shouldCastAsDateTimeImmutable()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 11
nc 1
nop 1
dl 0
loc 13
ccs 4
cts 4
cp 1
crap 1
rs 9.9
c 0
b 0
f 0
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 DateTimeImmutable;
26
use DateTimeInterface;
27
use Exception;
28
29
/**
30
 * @package Facebook
31
 */
32
class GraphNode
33
{
34
    /**
35
     * @var array maps object key names to Graph object types
36
     */
37
    protected static $graphNodeMap = [];
38
39
    /**
40
     * The fields contained in the node.
41
     *
42
     * @var array
43
     */
44
    private $fields = [];
45
46
    /**
47
     * Init this Graph object.
48
     *
49
     * @param array $data
50
     */
51 65
    public function __construct(array $data = [])
52
    {
53 65
        $this->fields = $this->castFields($data);
54 65
    }
55
56
    /**
57
     * Gets the value of a field from the Graph node.
58
     *
59
     * @param string $name    the field to retrieve
60
     * @param mixed  $default the default to return if the field doesn't exist
61
     *
62
     * @return mixed
63
     */
64 51
    public function getField($name, $default = null)
65
    {
66 51
        if (isset($this->fields[$name])) {
67 48
            return $this->fields[$name];
68
        }
69
70 3
        return $default;
71
    }
72
73
    /**
74
     * Returns a list of all fields set on the object.
75
     *
76
     * @return array
77
     */
78 1
    public function getFieldNames()
79
    {
80 1
        return array_keys($this->fields);
81
    }
82
83
    /**
84
     * Get all of the fields in the node.
85
     *
86
     * @return array
87
     */
88
    public function getFields()
89
    {
90
        return $this->fields;
91
    }
92
93
    /**
94
     * Get all fields as a plain array.
95
     *
96
     * @return array
97
     */
98
    public function asArray()
99
    {
100 8
        return array_map(function ($value) {
101 7
            if ($value instanceof GraphNode || $value instanceof GraphEdge) {
102
                return $value->asArray();
103
            }
104
105 7
            return $value;
106 8
        }, $this->fields);
107
    }
108
109
    /**
110
     * Convert the collection to its string representation.
111
     *
112
     * @return string
113
     */
114 2
    public function __toString()
115
    {
116 2
        return json_encode($this->uncastFields());
117
    }
118
119
    /**
120
     * Getter for $graphNodeMap.
121
     *
122
     * @return array
123
     */
124 19
    public static function getNodeMap()
125
    {
126 19
        return static::$graphNodeMap;
127
    }
128
129
    /**
130
     * Iterates over an array and detects the types each node
131
     * should be cast to and returns all the fields as an array.
132
     *
133
     * @TODO Add auto-casting to AccessToken entities.
134
     *
135
     * @param array $data the array to iterate over
136
     *
137
     * @return array
138
     */
139 65
    private function castFields(array $data)
140
    {
141 65
        $fields = [];
142
143 65
        foreach ($data as $k => $v) {
144 64
            if ($this->shouldCastAsDateTimeImmutable($k)
145 25
                && (is_numeric($v)
146 64
                    || $this->isIso8601DateString($v))
147
            ) {
148 23
                $fields[$k] = $this->castToDateTimeImmutable($v);
149 46
            } elseif ($k === 'birthday') {
150 4
                $fields[$k] = $this->castToBirthday($v);
151
            } else {
152 64
                $fields[$k] = $v;
153
            }
154
        }
155
156 65
        return $fields;
157
    }
158
159
    /**
160
     * Uncasts any auto-casted datatypes.
161
     * Basically the reverse of castFields().
162
     *
163
     * @return array
164
     */
165 2
    private function uncastFields()
166
    {
167 2
        $fields = $this->asArray();
168
169 2
        return array_map(function ($v) {
170 2
            if ($v instanceof \DateTime) {
171
                return $v->format(\DateTime::ISO8601);
172
            }
173
174 2
            return $v;
175 2
        }, $fields);
176
    }
177
178
    /**
179
     * Determines if a value from Graph should be cast to DateTimeImmutable.
180
     *
181
     * @param string $key
182
     *
183
     * @return bool
184
     */
185 64
    private function shouldCastAsDateTimeImmutable($key)
186
    {
187 64
        return in_array($key, [
188 64
            'created_time',
189
            'updated_time',
190
            'start_time',
191
            'stop_time',
192
            'end_time',
193
            'backdated_time',
194
            'issued_at',
195
            'expires_at',
196
            'publish_time'
197 64
        ], true);
198
    }
199
200
    /**
201
     * Detects an ISO 8601 formatted string.
202
     *
203
     * @param string $string
204
     *
205
     * @return bool
206
     *
207
     * @see https://developers.facebook.com/docs/graph-api/using-graph-api/#readmodifiers
208
     * @see http://www.cl.cam.ac.uk/~mgk25/iso-time.html
209
     * @see http://en.wikipedia.org/wiki/ISO_8601
210
     */
211 23
    private function isIso8601DateString($string)
212
    {
213
        // This insane regex was yoinked from here:
214
        // http://www.pelagodesign.com/blog/2009/05/20/iso-8601-date-validation-that-doesnt-suck/
215
        // ...and I'm all like:
216
        // http://thecodinglove.com/post/95378251969/when-code-works-and-i-dont-know-why
217
        $crazyInsaneRegexThatSomehowDetectsIso8601 = '/^([\+-]?\d{4}(?!\d{2}\b))'
218
            . '((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?'
219
            . '|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d'
220
            . '|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])'
221
            . '((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d'
222 23
            . '([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/';
223
224 23
        return preg_match($crazyInsaneRegexThatSomehowDetectsIso8601, $string) === 1;
225
    }
226
227
    /**
228
     * Casts a date value from Graph to DateTime.
229
     *
230
     * @param int|string $value
231
     *
232
     * @return DateTimeInterface
233
     * @throws Exception
234
     */
235 23
    private function castToDateTimeImmutable($value)
236
    {
237 23
        if (is_int($value)) {
238 2
            $dt = new DateTimeImmutable();
239 2
            $dt->setTimestamp($value);
240
        } else {
241 21
            $dt = new DateTimeImmutable($value);
242
        }
243
244 23
        return $dt;
245
    }
246
247
    /**
248
     * Casts a birthday value from Graph to Birthday.
249
     *
250
     * @param string $value
251
     *
252
     * @return Birthday
253
     * @throws Exception
254
     */
255 4
    private function castToBirthday($value)
256
    {
257 4
        return new Birthday($value);
258
    }
259
}
260