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, bool $strict=false) |
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
|
|
|
if ($strict) { |
45
|
|
|
throw new InvalidArgumentException( |
46
|
|
|
get_called_class() . "->$field does not exist" |
47
|
|
|
); |
48
|
|
|
} |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
private function fieldException($field, $message) |
52
|
|
|
{ |
53
|
|
|
return new InvalidArgumentException( |
54
|
|
|
get_called_class()."->$field must $message" |
55
|
|
|
); |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
protected function setField(string $field, $value, bool $strict=true) |
59
|
|
|
{ |
60
|
|
|
if ($field == '@context') { |
61
|
|
|
return; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$type = static::fieldType($field, $strict); |
65
|
|
|
if (!$type) { |
66
|
|
|
return; |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
if (is_null($value)) { |
70
|
|
|
$value = null; |
71
|
|
|
} elseif (is_array($type)) { # Set or Listing |
72
|
|
|
if ($type[0] == 'Set') { |
73
|
|
|
if (is_array($value)) { |
74
|
|
|
$class = 'JSKOS\\'.$type[1]; |
75
|
|
|
$value = new Set( |
76
|
|
|
array_map(function ($m) use ($class) { |
77
|
|
|
if (is_null($m)) { |
78
|
|
|
return null; |
79
|
|
|
} |
80
|
|
|
if ($m instanceof $class) { |
81
|
|
|
return $m; |
82
|
|
|
} |
83
|
|
|
return new $class($m); |
84
|
|
|
}, $value) |
85
|
|
|
); |
86
|
|
|
} elseif (!is_a($value, 'JSKOS\Set')) { |
87
|
|
|
throw $this->fieldException($field, "be a Set"); |
88
|
|
|
} else { |
|
|
|
|
89
|
|
|
# TODO: check member types |
90
|
|
|
} |
91
|
|
|
} else { # Listing |
92
|
|
|
if (is_array($value)) { |
93
|
|
|
$value = new Listing($value); |
94
|
|
|
} else { |
95
|
|
|
throw $this->fieldException($field, "be a Listing"); |
96
|
|
|
} |
97
|
|
|
# TODO: check member types |
98
|
|
|
} |
99
|
|
|
} elseif ($type == 'LanguageMapOfStrings') { |
100
|
|
|
if (!($value instanceof LanguageMapOfStrings)) { |
101
|
|
|
$value = new LanguageMapOfStrings($value); |
102
|
|
|
} |
103
|
|
|
} elseif ($type == 'LanguageMapOfLists') { |
104
|
|
|
if (!($value instanceof LanguageMapOfLists)) { |
105
|
|
|
$value = new LanguageMapOfLists($value); |
106
|
|
|
} |
107
|
|
|
} elseif (!DataType::hasType($value, $type)) { |
108
|
|
|
if ($type == 'ConceptScheme') { |
109
|
|
|
$value = new ConceptScheme($value); |
110
|
|
|
} else { |
111
|
|
|
throw $this->fieldException($field, "match JSKOS\DataType::is$type"); |
112
|
|
|
} |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
$this->$field = $value; |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
public function __set($field, $value) |
119
|
|
|
{ |
120
|
|
|
$this->setField($field, $value); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
public function &__get($field) |
124
|
|
|
{ |
125
|
|
|
$type = static::fieldType($field); |
126
|
|
|
if ($type) { |
127
|
|
|
if (is_null($this->$field)) { |
128
|
|
|
$null = null; |
129
|
|
|
return $null; |
130
|
|
|
} else { |
131
|
|
|
return $this->$field; |
132
|
|
|
} |
133
|
|
|
} else { |
134
|
|
|
trigger_error( |
135
|
|
|
"Undefined property: ".get_called_class()."::$$field", |
136
|
|
|
\E_USER_NOTICE |
137
|
|
|
); |
138
|
|
|
} |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
public function __isset($field): bool |
142
|
|
|
{ |
143
|
|
|
return !!static::fieldType($field); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* Check whether a given value looks like an URI. |
148
|
|
|
*/ |
149
|
|
|
public static function isURI($uri): bool |
150
|
|
|
{ |
151
|
|
|
return is_string($uri) && preg_match(IRI_PATTERN, $uri) === 1; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* Check whether a given value looks like an http/https URL. |
156
|
|
|
*/ |
157
|
|
|
public static function isURL($url): bool |
158
|
|
|
{ |
159
|
|
|
return is_string($url) && |
160
|
|
|
preg_match(IRI_PATTERN, $url, $match) && |
161
|
|
|
($match[2] == 'http' || $match[2] == 'https'); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
/** |
165
|
|
|
* Check whether a given value looks like a date. |
166
|
|
|
*/ |
167
|
|
|
public static function isDate($date): bool |
168
|
|
|
{ |
169
|
|
|
return is_string($date) && preg_match(DATE_PATTERN, $date) === 1; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Check whether a given value looks like a language tag. |
174
|
|
|
*/ |
175
|
|
|
public static function isLanguage($language): bool |
176
|
|
|
{ |
177
|
|
|
return is_string($language) && preg_match(LANGUAGE_PATTERN, $language) === 1; |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
/** |
181
|
|
|
* Check whether a given value looks like a language range. |
182
|
|
|
*/ |
183
|
|
|
public static function isLanguageRange($range): bool |
184
|
|
|
{ |
185
|
|
|
return is_string($range) && preg_match(LANGUAGE_RANGE_PATTERN, $range) === 1; |
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
/** |
189
|
|
|
* Check whether a given value looks like a language or language range. |
190
|
|
|
*/ |
191
|
|
|
public static function isLanguageOrRange($language): bool |
192
|
|
|
{ |
193
|
|
|
return is_string($language) && |
194
|
|
|
(preg_match(LANGUAGE_PATTERN, $language) === 1 || |
195
|
|
|
preg_match(LANGUAGE_RANGE_PATTERN, $language) === 1); |
196
|
|
|
} |
197
|
|
|
|
198
|
|
|
/** |
199
|
|
|
* Check whether a given value is a string. |
200
|
|
|
*/ |
201
|
|
|
public static function isString($string): bool |
202
|
|
|
{ |
203
|
|
|
return is_string($string); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Check whether a given value is a concept scheme |
208
|
|
|
*/ |
209
|
|
|
public static function isConceptScheme($scheme): bool |
210
|
|
|
{ |
211
|
|
|
return $scheme instanceof ConceptScheme; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
/** |
215
|
|
|
* Check whether a given value is of given type. |
216
|
|
|
*/ |
217
|
|
|
public static function hasType($value, string $type): bool |
218
|
|
|
{ |
219
|
|
|
$method = "is$type"; |
220
|
|
|
return self::$method($value); |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
This check looks for the
else
branches ofif
statements that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.These
else
branches can be removed.could be turned into
This is much more concise to read.