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

contributte / utils / 6877995345

15 Nov 2023 01:31PM UTC coverage: 91.765% (+0.6%) from 91.124%
6877995345

push

github

f3l1x
Composer: require PHP 8.1+

312 of 340 relevant lines covered (91.76%)

0.92 hits per line

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

84.0
/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
        public static ?bool $useReflection = null;
31

32
        public static bool $autoRefresh = true;
33

34
        /** @var string[] */
35
        public static array $inherited = ['description', 'param', 'return'];
36

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

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

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

55
                if ($res === []) {
1✔
56
                        return null;
×
57
                }
58

59
                return isset($res[$name]) ? end($res[$name]) : null;
1✔
60
        }
61

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

72
                } elseif ($r instanceof ReflectionMethod) {
1✔
73
                        $type = $r->getDeclaringClass()->getName();
1✔
74
                        $member = $r->getName();
1✔
75

76
                } elseif ($r instanceof ReflectionFunction) {
×
77
                        $type = null;
×
78
                        $member = $r->getName();
×
79

80
                } else {
81
                        $type = $r->getDeclaringClass()->getName();
×
82
                        $member = '$' . $r->getName();
×
83
                }
84

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

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

93
                $annotations = self::$useReflection ? self::parseComment((string) $r->getDocComment()) : [];
1✔
94

95
                if (
96
                        $r instanceof ReflectionMethod && !$r->isPrivate()
1✔
97
                        && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0])) // @phpstan-ignore-line
1✔
98
                        && $type !== null
1✔
99
                ) {
100
                        try {
101
                                $inherited = self::getAnnotations(new ReflectionMethod((string) get_parent_class($type), $member));
1✔
102
                        } catch (ReflectionException $e) {
1✔
103
                                try {
104
                                        $inherited = self::getAnnotations($r->getPrototype());
1✔
105
                                } catch (ReflectionException $e) {
1✔
106
                                        $inherited = [];
1✔
107
                                }
108
                        }
109

110
                        $annotations += array_intersect_key($inherited, array_flip(self::$inherited));
1✔
111
                }
112

113
                return self::$cache[$type][$member] = $annotations;
1✔
114
        }
115

116
        /**
117
         * @return array<array<string|int,mixed[]>>
118
         */
119
        private static function parseComment(string $comment): array
1✔
120
        {
121
                static $tokens = ['true' => true, 'false' => false, 'null' => null, '' => true];
1✔
122

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

127
                if ($parts === false) {
1✔
128
                        throw new LogicalException('Cannot split comment');
×
129
                }
130

131
                $description = trim($parts[0]);
1✔
132
                if ($description !== '') {
1✔
133
                        $res['description'] = [$description];
×
134
                }
135

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

146
                foreach ($matches as $match) {
1✔
147
                        [, $name, $value] = $match;
1✔
148

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

161
                                        } elseif (is_numeric($val)) {
1✔
162
                                                $val = 1 * $val;
×
163

164
                                        } else {
165
                                                $lval = strtolower($val);
1✔
166
                                                $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
1✔
167
                                        }
168

169
                                        if ($key === '') {
1✔
170
                                                $items[] = $val;
1✔
171

172
                                        } else {
173
                                                $items[$key] = $val;
×
174
                                        }
175
                                }
176

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

179
                        } else {
180
                                $value = trim($value);
1✔
181
                                if (is_numeric($value)) {
1✔
182
                                        $value = 1 * $value;
×
183

184
                                } else {
185
                                        $lval = strtolower($value);
1✔
186
                                        $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
1✔
187
                                }
188
                        }
189

190
                        $res[$name][] = is_array($value) ? ArrayHash::from($value) : $value;
1✔
191
                }
192

193
                return $res;
1✔
194
        }
195

196
}
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