Completed
Push — master ( 492437...5b2327 )
by Ben
24:19 queued 09:23
created

TypeChecker::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php declare(strict_types=1);
2
3
namespace Benrowe\Properties;
4
5
class TypeChecker
6
{
7
    private $value;
8
9
    const DOCBLOCK_PARAM_PATTERN = "/^(([a-z\\\])+(\[\])?\|?)+$/i";
10
11
    const TYPES = [
12
        'string',
13
        'integer',
14
        'int',
15
        'float',
16
        'boolean',
17
        'bool',
18
        'array',
19
        'object',
20
        'null',
21
        'resource',
22
    ];
23
24
    private $remap = [
25
        'int' => 'integer',
26
        'bool' => 'boolean',
27
    ];
28
29
    /**
30
     * Grabs the value to check
31
     */
32
    public function __construct($value)
33
    {
34
        $this->value = $value;
35
    }
36
37
    /**
38
     * Check the already supplied value against the type specified
39
     */
40
    public function check(string $type): bool
41
    {
42
        if (!preg_match("/^(([a-z\\\])+(\[\])?\|?)+$/i", $type)) {
43
            throw new TypeException("Unknown type check for \"$type\"");
44
        }
45
        $types = $this->normaliseTypes(explode('|', $type));
46
        foreach ($types as $type) {
47
            if (substr($type, -2) === '[]') {
48
                $type = substr($type, 0, -2);
49
                if ($this->isArrayOf($type, $this->value)) {
50
                    return true;
51
                }
52
            } else {
53
                if ($this->checkType($type, $this->value)) {
54
                    return true;
55
                }
56
            }
57
        }
58
59
        return false;
60
    }
61
62
    public function checkAsClosure(\Closure $closure): bool
63
    {
64
        $result = call_user_func($closure, $this->value, $this);
65
        if (gettype($result) !== 'boolean') {
66
            throw new TypeException('Return value for closure is not boolean');
67
        }
68
        return $result;
69
    }
70
71
    private function isArrayOf(string $type, $value): bool
72
    {
73
        if (!is_array($value)) {
74
            return false;
75
        }
76
77
        foreach ($value as $val) {
78
            if (!$this->checkType($type, $val)) {
79
                return false;
80
            }
81
        }
82
        return true;
83
    }
84
85
    private function checkType(string $type, $value): bool
86
    {
87
        if ($this->isBaseType($type)) {
88
            $calcType = gettype($value);
89
            if (array_key_exists($type, $this->remap)) {
90
                $type = $this->remap[$type];
91
            }
92
93
            return $calcType === $type;
94
        }
95
96
        return (bool)($value instanceof $type);
97
    }
98
99
    /**
100
     * Clean up the supplied array of types
101
     *
102
     * @param array $types
103
     *
104
     * @return array
105
     */
106
    private function normaliseTypes(array $types): array
107
    {
108
        return array_filter(array_map('trim', $types));
109
    }
110
111
    /**
112
     * Determine if the supplied type is one of php's base values
113
     *
114
     * @param string $type
115
     * @return bool
116
     */
117
    private function isBaseType(string $type): bool
118
    {
119
        return in_array($type, self::TYPES, true);
120
    }
121
}
122