Passed
Push — master ( b31e40...5c7143 )
by Christopher
07:14
created

BitMask::setFlag()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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