Passed
Push — master ( 1d0ea2...0f9d68 )
by Kacper
05:34
created

Validator::everywhere()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2.0932

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 10
ccs 5
cts 7
cp 0.7143
crap 2.0932
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Highlighter
7
 *
8
 * Copyright (C) 2016, Some right reserved.
9
 *
10
 * @author Kacper "Kadet" Donat <[email protected]>
11
 *
12
 * Contact with author:
13
 * Xmpp: [email protected]
14
 * E-mail: [email protected]
15
 *
16
 * From Kadet with love.
17
 */
18
19
namespace Kadet\Highlighter\Parser\Validator;
20
21
use Kadet\Highlighter\Parser\Context;
22
23
class Validator
24
{
25
    public const CONTEXT_NOT_IN    = 2;
26
    public const CONTEXT_IN        = 1;
27
    public const CONTEXT_IN_ONE_OF = 4;
28
    public const CONTEXT_EXACTLY   = 8;
29
    public const CONTEXT_ON_TOP    = 16;
30
    public const CONTEXT_REGEX     = 32;
31
32
    private $_rules = [];
33
34
    /**
35
     * Validator constructor.
36
     *
37
     * @param array $rules
38
     */
39 50
    public function __construct(array $rules = [])
40
    {
41 50
        $this->setRules($rules);
42 50
    }
43
44 46
    public function validate(Context $context, $additional = [])
45
    {
46 46
        return $this->_validate($context->stack, $additional + $this->_rules);
47
    }
48
49 50
    public function setRules($rules)
50
    {
51 50
        if (empty($rules)) {
52 40
            $this->_rules = [ 'none' => Validator::CONTEXT_IN_ONE_OF ];
53
        } else {
54 13
            foreach ($rules as $key => $rule) {
55 13
                list($plain, $type)   = $this->_parse($rule);
56 13
                $this->_rules[$plain] = $type;
57
            }
58
        }
59 50
    }
60
61 44
    private function _clean($rule, &$required)
62
    {
63 44
        if (strpos($rule, '.') !== false) {
64 20
            $parents = array_filter(array_keys($required), function ($key) use ($rule) {
65 20
                return fnmatch($key . '.*', $rule);
66 20
            });
67
68 20
            foreach ($parents as $remove) {
69 17
                unset($required[$remove]);
70
            }
71
        }
72 44
    }
73
74 49
    protected function _validate($context, $rules, $result = false)
75
    {
76 49
        if (empty($context)) {
77 43
            $context = ['none'];
78
        }
79
80 49
        foreach ($rules as $rule => &$type) {
81 46
            $matched = $this->_matches($context, $rule, $type);
82
83 46
            if ($type & Validator::CONTEXT_NOT_IN) {
84 27
                if ($matched) {
85 20
                    return false;
86
                }
87 26
                $result = true;
88 44
            } elseif ($type & Validator::CONTEXT_IN) {
89 28
                if (!$matched) {
90 21
                    return false;
91
                }
92 26
                $result = true;
93
94 26
                $this->_clean($rule, $rules);
95 37
            } elseif ($type & Validator::CONTEXT_IN_ONE_OF) {
96 37
                if ($matched) {
97 36
                    $result = true;
98 36
                    $this->_clean($rule, $rules);
99
                }
100
            }
101
        }
102
103 48
        return $result;
104
    }
105
106 13
    private function _parse($rule)
107
    {
108
        $types = [
109 13
            '!' => Validator::CONTEXT_NOT_IN,
110
            '+' => Validator::CONTEXT_IN,
111
            '*' => Validator::CONTEXT_IN_ONE_OF,
112
            '@' => Validator::CONTEXT_EXACTLY,
113
//            '^' => Validator::CONTEXT_ON_TOP,
114
            '~' => Validator::CONTEXT_REGEX
115
        ];
116
117 13
        if (!isset($types[$rule[0]])) {
118 5
            return [$rule, Validator::CONTEXT_IN];
119
        }
120
121 11
        $type = 0;
122 11
        $pos  = 0;
123 11
        foreach (str_split($rule) as $pos => $char) {
124 11
            if (!isset($types[$char])) {
125 11
                break;
126
            }
127
128 11
            $type |= $types[$char];
129
        }
130
131 11
        $rule = substr($rule, $pos);
132
133 11
        if ($type & self::CONTEXT_REGEX) {
134 2
            $rule = "/^$rule(\\.\\w+)?/i";
135
        }
136
137 11
        return [$rule, $type];
138
    }
139
140 46
    private function _matches($context, $rule, $type)
141
    {
142 46
        if ($type & self::CONTEXT_EXACTLY) {
143 1
            return in_array($rule, $context, true);
144 45
        } elseif ($type & self::CONTEXT_REGEX) {
145 2
            foreach ($context as $item) {
146 2
                if (preg_match($rule, $item)) {
147 2
                    return true;
148
                }
149
            }
150 2
            return false;
151
        } else {
152 43
            if (in_array($rule, $context, true)) {
153 43
                return true;
154
            }
155
156 34
            foreach ($context as $item) {
157 34
                if (fnmatch("$rule.*", $item)) {
158 15
                    return true;
159
                }
160
            }
161 34
            return false;
162
        }
163
    }
164
165 54
    public static function everywhere()
166
    {
167 54
        static $validator;
168 54
        if (!$validator) {
169
            $validator = new DelegateValidator(function () {
170 38
                return true;
171
            });
172
        }
173
174 54
        return $validator;
175
    }
176
}
177