• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

contributte / utils / 3781456711

pending completion
3781456711

push

github

Milan Felix Å ulc
Annotations: get/has

10 of 10 new or added lines in 1 file covered. (100.0%)

308 of 338 relevant lines covered (91.12%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.67
/src/Annotations.php
1
<?php declare(strict_types = 1);
2

3
namespace Contributte\Utils;
4

5
use Contributte\Utils\Exception\LogicalException;
6
use Nette\StaticClass;
7
use Nette\Utils\ArrayHash;
8
use Nette\Utils\Strings;
9
use ReflectionClass;
10
use ReflectionException;
11
use ReflectionFunction;
12
use ReflectionMethod;
13
use ReflectionProperty;
14
use Reflector;
15

16
/**
17
 * @creator David Grudl https://github.com/nette/reflection
18
 */
19
class Annotations
20
{
21

22
        use StaticClass;
23

24
        /** @internal single & double quoted PHP string */
25
        public const RE_STRING = '\'(?:\\\\.|[^\'\\\\])*\'|"(?:\\\\.|[^"\\\\])*"';
26

27
        /** @internal identifier */
28
        public const RE_IDENTIFIER = '[_a-zA-Z\x7F-\xFF][_a-zA-Z0-9\x7F-\xFF-\\\]*';
29

30
        /** @var bool */
31
        public static $useReflection;
32

33
        /** @var bool */
34
        public static $autoRefresh = true;
35

36
        /** @var string[] */
37
        public static $inherited = ['description', 'param', 'return'];
38

39
        /** @var array<string, array<string, array<string, array<mixed>>>> */
40
        private static $cache;
41

42
        /**
43
         * @param ReflectionClass<object>|ReflectionMethod|ReflectionProperty|ReflectionFunction $r
44
         */
45
        public static function hasAnnotation(Reflector $r, string $name): bool
1✔
46
        {
47
                return self::getAnnotation($r, $name) !== null;
1✔
48
        }
49

50
        /**
51
         * @param ReflectionClass<object>|ReflectionMethod|ReflectionProperty|ReflectionFunction $r
52
         * @return mixed
53
         */
54
        public static function getAnnotation(Reflector $r, string $name)
1✔
55
        {
56
                $res = self::getAnnotations($r);
1✔
57

58
                if ($res === []) {
1✔
59
                        return null;
×
60
                }
61

62
                return isset($res[$name]) ? end($res[$name]) : null;
1✔
63
        }
64

65
        /**
66
         * @param ReflectionClass<object>|ReflectionMethod|ReflectionProperty|ReflectionFunction $r
67
         * @return array<array<mixed>>
68
         */
69
        public static function getAnnotations(Reflector $r): array
1✔
70
        {
71
                if ($r instanceof ReflectionClass) {
1✔
72
                        $type = $r->getName();
1✔
73
                        $member = 'class';
1✔
74

75
                } elseif ($r instanceof ReflectionMethod) {
1✔
76
                        $type = $r->getDeclaringClass()->getName();
1✔
77
                        $member = $r->getName();
1✔
78

79
                } elseif ($r instanceof ReflectionFunction) {
×
80
                        $type = null;
×
81
                        $member = $r->getName();
×
82

83
                } else {
84
                        $type = $r->getDeclaringClass()->getName();
×
85
                        $member = '$' . $r->getName();
×
86
                }
87

88
                if (self::$useReflection === null) { // detects whether is reflection available
1✔
89
                        self::$useReflection = (bool) (new ReflectionClass(self::class))->getDocComment();
1✔
90
                }
91

92
                if (isset(self::$cache[$type][$member])) { // is value cached?
1✔
93
                        return self::$cache[$type][$member];
1✔
94
                }
95

96
                if (self::$useReflection) {
1✔
97
                        $annotations = self::parseComment((string) $r->getDocComment());
1✔
98
                } else {
99
                        $annotations = [];
×
100
                }
101

102
                // @phpstan-ignore-next-line
103
                if ($r instanceof ReflectionMethod && !$r->isPrivate() && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0]))
1✔
104
                ) {
105
                        try {
106
                                $inherited = self::getAnnotations(new ReflectionMethod((string) get_parent_class($type), $member));
1✔
107
                        } catch (ReflectionException $e) {
1✔
108
                                try {
109
                                        $inherited = self::getAnnotations($r->getPrototype());
1✔
110
                                } catch (ReflectionException $e) {
1✔
111
                                        $inherited = [];
1✔
112
                                }
113
                        }
114

115
                        $annotations += array_intersect_key($inherited, array_flip(self::$inherited));
1✔
116
                }
117

118
                return self::$cache[$type][$member] = $annotations;
1✔
119
        }
120

121
        /**
122
         * @return array<array<string|int,mixed[]>>
123
         */
124
        private static function parseComment(string $comment): array
1✔
125
        {
126
                static $tokens = ['true' => true, 'false' => false, 'null' => null, '' => true];
1✔
127

128
                $res = [];
1✔
129
                $comment = (string) preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
1✔
130
                $parts = preg_split('#^\s*(?=@' . self::RE_IDENTIFIER . ')#m', $comment, 2);
1✔
131

132
                if ($parts === false) {
1✔
133
                        throw new LogicalException('Cannot split comment');
×
134
                }
135

136
                $description = trim($parts[0]);
1✔
137
                if ($description !== '') {
1✔
138
                        $res['description'] = [$description];
×
139
                }
140

141
                $matches = Strings::matchAll(
1✔
142
                        $parts[1] ?? '',
1✔
143
                        '~
144
                                (?<=\s|^)@(' . self::RE_IDENTIFIER . ')[ \t]*      ##  annotation
145
                                (
146
                                        \((?>' . self::RE_STRING . '|[^\'")@]+)+\)|  ##  (value)
1✔
147
                                        [^(@\r\n][^@\r\n]*|)                     ##  value
148
                        ~xi'
149
                );
150

151
                foreach ($matches as $match) {
1✔
152
                        [, $name, $value] = $match;
1✔
153

154
                        if (substr($value, 0, 1) === '(') {
1✔
155
                                $items = [];
1✔
156
                                $key = '';
1✔
157
                                $val = true;
1✔
158
                                $value[0] = ',';
1✔
159
                                while ($m = Strings::match($value, '#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')) {
1✔
160
                                        $value = substr($value, strlen($m[0]));
1✔
161
                                        [, $key, $val] = $m;
1✔
162
                                        $val = rtrim($val);
1✔
163
                                        if ($val[0] === "'" || $val[0] === '"') {
1✔
164
                                                $val = substr($val, 1, -1);
×
165

166
                                        } elseif (is_numeric($val)) {
1✔
167
                                                $val = 1 * $val;
×
168

169
                                        } else {
170
                                                $lval = strtolower($val);
1✔
171
                                                $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
1✔
172
                                        }
173

174
                                        if ($key === '') {
1✔
175
                                                $items[] = $val;
1✔
176

177
                                        } else {
178
                                                $items[$key] = $val;
×
179
                                        }
180
                                }
181

182
                                $value = count($items) < 2 && $key === '' ? $val : $items;
1✔
183

184
                        } else {
185
                                $value = trim($value);
1✔
186
                                if (is_numeric($value)) {
1✔
187
                                        $value = 1 * $value;
×
188

189
                                } else {
190
                                        $lval = strtolower($value);
1✔
191
                                        $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
1✔
192
                                }
193
                        }
194

195
                        $res[$name][] = is_array($value) ? ArrayHash::from($value) : $value;
1✔
196
                }
197

198
                return $res;
1✔
199
        }
200

201
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc