DataType::setField()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 3
nc 3
nop 3
1
<?php declare(strict_types = 1);
2
3
namespace JSKOS;
4
5
use InvalidArgumentException;
6
7
const IRI_PATTERN = '!^((?P<scheme>[a-z][a-z0-9+.-]*):)' .
8
        '((?P<doubleslash>//)(?P<authority>[^/?#]*))?(?P<path>[^?#]*)' .
9
        '((?<querydef>\?)(?P<query>[^#]*))?(#(?P<fragment>.*))?!';
10
11
const DATE_PATTERN = '!^(?P<year>-?\d\d\d\d)' .
12
        '(-((?P<month>\d\d)(' .
13
        '-(?P<day>\d\d)' .
14
        '(T\d\d:\d\d:\d\d(\.\d+)?)?' .
15
        '(Z|[+-]\d\d:\d\d)?' .
16
        ')?))?$!';
17
18
const LANGUAGE_PATTERN = '/^[a-z]{2,3}(?:-[A-Z]{2,3}(?:-[a-zA-Z]{4})?)?$/';
19
20
const LANGUAGE_RANGE_PATTERN = '/^([a-z]{2,3}(?:-[A-Z]{2,3}(?:-[a-zA-Z]{4})?)?)?-$/';
21
22
/**
23
 * Base class of JSKOS Resources and fields.
24
 */
25
abstract class DataType extends PrettyJsonSerializable
26
{
27
    use Constructor;
28
29
    const FIELDS = [];
30
31
    /**
32
     * Get field definition from FIELDS, including parent classes.
33
     */
34
    protected static function fieldType(string $field)
35
    {
36
        $class = get_called_class();
37
        while ($class && $class != self::class) {
38
            if (isset($class::FIELDS[$field])) {
39
                return $class::FIELDS[$field];
40
            }
41
            $class = get_parent_class($class);
42
        }
43
    }
44
45
    private function fieldException(string $field, string $message)
46
    {
47
        return new InvalidArgumentException(
48
            get_called_class() . "->$field must $message"
49
        );
50
    }
51
52
    protected function setField($field, $value, bool $strict = true)
53
    {
54
        try {
55
            $this->setFieldStrict("$field", $value);
56
        } catch (InvalidArgumentException $e) {
57
            if ($strict) {
58
                throw $e;
59
            }
60
        }
61
    }
62
63
    protected function setFieldStrict(string $field, $value)
64
    {
65
        if ($field == '@context') {
66
            return;
67
        }
68
69
        $type = static::fieldType($field);
70
        if (!$type) {
71
            throw new InvalidArgumentException(
72
                get_called_class() . "->$field does not exist"
73
            );
74
        }
75
76
        if (is_null($value)) {
77
            $value = null;
78
        } elseif (is_array($type)) { # Set or Listing
79
            if ($type[0] == 'Set') {
80
                if (!($value instanceof Set)) {
81
                    if (is_array($value)) {
82
                        $class = 'JSKOS\\' . $type[1];
83
                        $value = new Set(
84
                            array_map(function ($m) use ($class) {
85
                                if (is_null($m)) {
86
                                    return null;
87
                                }
88
                                if ($m instanceof $class) {
89
                                    return $m;
90
                                }
91
                                return new $class($m);
92
                            }, $value)
93
                        );
94
                    } else {
95
                        throw $this->fieldException($field, "be a Set");
96
                    }
97
                }
98
                # TODO: check member types
99
            } else { # Listing
100
                if (!($value instanceof Listing)) {
101
                    if (is_array($value)) {
102
                        $value = new Listing($value);
103
                    } else {
104
                        throw $this->fieldException($field, "be a Listing");
105
                    }
106
                }
107
                # TODO: check member types
108
            }
109
        } elseif (in_array($type, ['LanguageMapOfStrings', 'LanguageMapOfLists', 'ConceptScheme', 'Item'])) {
110
            $type = "JSKOS\\$type";
111
            if (!($value instanceof $type)) {
112
                $value = new $type($value);
113
            }
114
        } elseif ($type != '*' && !DataType::hasType($value, $type)) {
115
            throw $this->fieldException($field, "match JSKOS\DataType::is$type");
116
        }
117
118
        $this->$field = $value;
119
    }
120
121
    public function __set($field, $value)
122
    {
123
        $this->setField($field, $value);
124
    }
125
126
    public function &__get($field)
127
    {
128
        $type = static::fieldType($field);
129
        if ($type) {
130
            if (is_null($this->$field)) {
131
                $null = null;
132
                return $null;
133
            } else {
134
                return $this->$field;
135
            }
136
        } else {
137
            trigger_error(
138
                "Undefined property: " . get_called_class() . "::$$field",
139
                \E_USER_NOTICE
140
            );
141
        }
142
    }
143
144
    public function __isset($field): bool
145
    {
146
        return !!static::fieldType($field);
147
    }
148
149
    /**
150
     * Check whether a given value looks like an URI.
151
     */
152
    public static function isURI($uri): bool
153
    {
154
        return is_string($uri) && preg_match(IRI_PATTERN, $uri) === 1;
155
    }
156
157
    /**
158
     * Check whether a given value looks like an http/https URL.
159
     */
160
    public static function isURL($url): bool
161
    {
162
        return is_string($url) &&
163
               preg_match(IRI_PATTERN, $url, $match) &&
164
               ($match[2] == 'http' || $match[2] == 'https');
165
    }
166
167
    /**
168
     * Check whether a given value looks like a date.
169
     */
170
    public static function isDate($date): bool
171
    {
172
        return is_string($date) && preg_match(DATE_PATTERN, $date) === 1;
173
    }
174
175
    /**
176
     * Check whether a given value looks like a language tag.
177
     */
178
    public static function isLanguage($language): bool
179
    {
180
        return is_string($language) && preg_match(LANGUAGE_PATTERN, $language) === 1;
181
    }
182
183
    /**
184
     * Check whether a given value looks like a language range.
185
     */
186
    public static function isLanguageRange($range): bool
187
    {
188
        return is_string($range) && preg_match(LANGUAGE_RANGE_PATTERN, $range) === 1;
189
    }
190
191
    /**
192
     * Check whether a given value looks like a language or language range.
193
     */
194
    public static function isLanguageOrRange($language): bool
195
    {
196
        return is_string($language) &&
197
               (preg_match(LANGUAGE_PATTERN, $language) === 1 ||
198
               preg_match(LANGUAGE_RANGE_PATTERN, $language) === 1);
199
    }
200
201
    /**
202
     * Check whether a given value is a string.
203
     */
204
    public static function isString($string): bool
205
    {
206
        return is_string($string);
207
    }
208
209
    /**
210
     * Check whether a given value is a non-negative integer
211
     */
212
    public static function isNonNegativeInteger($value): bool
213
    {
214
        return is_int($value) and $value >= 0;
215
    }
216
217
    /**
218
     * Check whether a given value is a percentage
219
     */
220
    public static function isPercentage($value): bool
221
    {
222
        return (is_int($value) || is_float($value)) and $value >= 0.0 and $value <= 1.0;
223
    }
224
225
    /**
226
     * Check whether a given value is a concept scheme
227
     */
228
    public static function isConceptScheme($scheme): bool
229
    {
230
        return $scheme instanceof ConceptScheme;
231
    }
232
233
    /**
234
     * Check whether a given value is of given type.
235
     */
236
    public static function hasType($value, string $type): bool
237
    {
238
        $method = "is$type";
239
        return self::$method($value);
240
    }
241
}
242