BitMask::isValid()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 3
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 6
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Cruxinator\BitMask;
6
7
use BadMethodCallException;
8
use MyCLabs\Enum\Enum;
9
use ReflectionException;
10
use UnexpectedValueException;
11
12
abstract class BitMask extends Enum
13
{
14
    // Because 0 is the identity element when ORing a bitmask, we have to special-case it when ANDing bitmasks
15
    // (akin to ordinary division by 0, the addition identity element on integers/rationals/reals)
16
17
    protected static function assertValidValueReturningKey($value): string
18
    {
19
        if (!static::isValid($value)) {
20
            throw new UnexpectedValueException("Value '$value' is not part of the enum ".static::class);
21
        }
22
23
        return implode('|', self::getKeyArray($value));
24
    }
25
26
    /**
27
     * @param $value
28
     *
29
     * @return bool
30
     */
31
    public static function isValid($value): bool
32
    {
33
        $min = min(static::toArray());
34
        $max = max(static::toArray()) * 2 - 1;
35
36
        return $value >= $min && $value <= $max;
37
    }
38
39
    /**
40
     * @param $name
41
     * @param $arguments
42
     *
43
     * @throws ReflectionException
44
     *
45
     * @return bool|self
46
     */
47
    public function __call($name, $arguments)
48
    {
49
        $array = static::toArray();
50
        $regexBase = '/(isComponentOf|is|set)(%s)/m';
51
        $regexFull = sprintf($regexBase, implode('$|', array_keys($array)));
52
        preg_match($regexFull, $name, $match);
53
        if (count($match) > 0 && $match[0] === $name) {
54
            return $this->{$match[1].'Flag'}($array[$match[2]], $arguments[0] ?? true);
55
        }
56
57
        throw new BadMethodCallException(sprintf('Enum %s not found on %s', $name, get_class($this)));
58
    }
59
60
    /**
61
     * @return array
62
     */
63
    public static function toArray(): array
64
    {
65
        $firstTime = !isset(static::$cache[static::class]);
66
        $array = array_filter(parent::toArray(), static function ($value): bool {
67
            return is_scalar($value);
68
        });
69
        $firstTime && array_walk($array, static function ($item): void {
70
            if (!is_int($item)) {
71
                throw new UnexpectedValueException(
72
                    sprintf('All defined Const on Enum %s should be integers', static::class)
73
                );
74
            }
75
        });
76
77
        return $array;
78
    }
79
80
    /**
81
     * @return string
82
     */
83
    public function getKey(): string
84
    {
85
        return implode('|', self::getKeyArray($this->value));
86
    }
87
88
    /**
89
     * @param mixed $value
90
     *
91
     * @return array
92
     */
93
    public static function getKeyArray(mixed $value): array
94
    {
95
        $f = array_filter(static::toArray(), static function ($key) use (&$value) {
96
            return $value & $key;
97
        });
98
99
        return array_keys($f);
100
    }
101
102
    /**
103
     * @throws ReflectionException
104
     *
105
     * @return string
106
     */
107
    public function __toString(): string
108
    {
109
        $name = $this->getName();
110
        $array = static::toArray();
111
        $ret = '';
112
        foreach ($array as $key => $value) {
113
            $ret .= "'".$key."' => ".($this->{'is'.$key}() ? 'TRUE' : 'FALSE').PHP_EOL;
114
        }
115
116
        return $name.'['.PHP_EOL.
117
            $ret.
118
            ']'.PHP_EOL;
119
    }
120
121
    public function getName(): string
122
    {
123
        $path = explode('\\', __CLASS__);
124
125
        return array_pop($path);
126
    }
127
128
    protected function isFlag(int $flag): bool
129
    {
130
        return 0 === $flag ? 0 === $this->value : (($this->value & $flag) === $flag);
131
    }
132
133
    protected function isComponentOfFlag(int $flag): bool
134
    {
135
        return 0 === $flag ? $flag === $this->value : (($this->value & $flag) === $this->value);
136
    }
137
138
    protected function setFlag(int $flag, bool $value): static
139
    {
140
        if ($value) {
141
            $this->value = 0 === $flag ? 0 : $this->value | $flag;
142
        } else {
143
            $this->value = 0 === $flag ? $this->value : $this->value & ~$flag;
144
        }
145
146
        return $this;
147
    }
148
}
149