Completed
Push — master ( be3539...07ea50 )
by Jakob
03:34
created

DataType::fieldException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
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(string $field, string $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 (!($value instanceof Set)) {
74
                    if (is_array($value)) {
75
                        $class = 'JSKOS\\' . $type[1];
76
                        $value = new Set(
77
                            array_map(function ($m) use ($class) {
78
                                if (is_null($m)) {
79
                                    return null;
80
                                }
81
                                if ($m instanceof $class) {
82
                                    return $m;
83
                                }
84
                                return new $class($m);
85
                            }, $value)
86
                        );
87
                    } else {
88
                        throw $this->fieldException($field, "be a Set");
89
                    }
90
                }
91
                # TODO: check member types
92
            } else { # Listing
93
                if (!($value instanceof Listing)) {
94
                    if (is_array($value)) {
95
                        $value = new Listing($value);
96
                    } else {
97
                        throw $this->fieldException($field, "be a Listing");
98
                    }
99
                }
100
                # TODO: check member types
101
            }
102
        } elseif (in_array($type, ['LanguageMapOfStrings', 'LanguageMapOfLists', 'ConceptScheme', 'Item'])) {
103
            $type = "JSKOS\\$type";
104
            if (!($value instanceof $type)) {
105
                $value = new $type($value);
106
            }
107
        } elseif ($type != '*' && !DataType::hasType($value, $type)) {
108
            throw $this->fieldException($field, "match JSKOS\DataType::is$type");
109
        }
110
111
        $this->$field = $value;
112
    }
113
114
    public function __set($field, $value)
115
    {
116
        $this->setField($field, $value);
117
    }
118
119
    public function &__get($field)
120
    {
121
        $type = static::fieldType($field);
122
        if ($type) {
123
            if (is_null($this->$field)) {
124
                $null = null;
125
                return $null;
126
            } else {
127
                return $this->$field;
128
            }
129
        } else {
130
            trigger_error(
131
                "Undefined property: " . get_called_class() . "::$$field",
132
                \E_USER_NOTICE
133
            );
134
        }
135
    }
136
137
    public function __isset($field): bool
138
    {
139
        return !!static::fieldType($field);
140
    }
141
142
    /**
143
     * Check whether a given value looks like an URI.
144
     */
145
    public static function isURI($uri): bool
146
    {
147
        return is_string($uri) && preg_match(IRI_PATTERN, $uri) === 1;
148
    }
149
150
    /**
151
     * Check whether a given value looks like an http/https URL.
152
     */
153
    public static function isURL($url): bool
154
    {
155
        return is_string($url) &&
156
               preg_match(IRI_PATTERN, $url, $match) &&
157
               ($match[2] == 'http' || $match[2] == 'https');
158
    }
159
160
    /**
161
     * Check whether a given value looks like a date.
162
     */
163
    public static function isDate($date): bool
164
    {
165
        return is_string($date) && preg_match(DATE_PATTERN, $date) === 1;
166
    }
167
168
    /**
169
     * Check whether a given value looks like a language tag.
170
     */
171
    public static function isLanguage($language): bool
172
    {
173
        return is_string($language) && preg_match(LANGUAGE_PATTERN, $language) === 1;
174
    }
175
176
    /**
177
     * Check whether a given value looks like a language range.
178
     */
179
    public static function isLanguageRange($range): bool
180
    {
181
        return is_string($range) && preg_match(LANGUAGE_RANGE_PATTERN, $range) === 1;
182
    }
183
184
    /**
185
     * Check whether a given value looks like a language or language range.
186
     */
187
    public static function isLanguageOrRange($language): bool
188
    {
189
        return is_string($language) &&
190
               (preg_match(LANGUAGE_PATTERN, $language) === 1 ||
191
               preg_match(LANGUAGE_RANGE_PATTERN, $language) === 1);
192
    }
193
194
    /**
195
     * Check whether a given value is a string.
196
     */
197
    public static function isString($string): bool
198
    {
199
        return is_string($string);
200
    }
201
202
    /**
203
     * Check whether a given value is a non-negative integer
204
     */
205
    public static function isNonNegativeInteger($value): bool
206
    {
207
        return is_int($value) and $value >= 0;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
208
    }
209
210
    /**
211
     * Check whether a given value is a percentage
212
     */
213
    public static function isPercentage($value): bool
214
    {
215
        return (is_int($value) || is_float($value)) and $value >= 0.0 and $value <= 1.0;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
216
    }
217
218
    /**
219
     * Check whether a given value is a concept scheme
220
     */
221
    public static function isConceptScheme($scheme): bool
222
    {
223
        return $scheme instanceof ConceptScheme;
224
    }
225
226
    /**
227
     * Check whether a given value is of given type.
228
     */
229
    public static function hasType($value, string $type): bool
230
    {
231
        $method = "is$type";
232
        return self::$method($value);
233
    }
234
}
235