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

contributte / utils / 3781306601

pending completion
3781306601

push

github

Milan Felix Ć ulc
Backport annotations from nette/reflection

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

301 of 331 relevant lines covered (90.94%)

0.91 hits per line

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

80.88
/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, mixed[]>> */
40
        private static $cache;
41

42
        /**
43
         * @param ReflectionClass<object>|ReflectionMethod|ReflectionProperty|ReflectionFunction $r
44
         * @return array<mixed>
45
         */
46
        public static function getAll(Reflector $r): array
1✔
47
        {
48
                if ($r instanceof ReflectionClass) {
1✔
49
                        $type = $r->getName();
1✔
50
                        $member = 'class';
1✔
51

52
                } elseif ($r instanceof ReflectionMethod) {
1✔
53
                        $type = $r->getDeclaringClass()->getName();
1✔
54
                        $member = $r->getName();
1✔
55

56
                } elseif ($r instanceof ReflectionFunction) {
×
57
                        $type = null;
×
58
                        $member = $r->getName();
×
59

60
                } else {
61
                        $type = $r->getDeclaringClass()->getName();
×
62
                        $member = '$' . $r->getName();
×
63
                }
64

65
                if (self::$useReflection === null) { // detects whether is reflection available
1✔
66
                        self::$useReflection = (bool) (new ReflectionClass(self::class))->getDocComment();
1✔
67
                }
68

69
                if (isset(self::$cache[$type][$member])) { // is value cached?
1✔
70
                        return self::$cache[$type][$member];
×
71
                }
72

73
                if (self::$useReflection) {
1✔
74
                        $annotations = self::parseComment((string) $r->getDocComment());
1✔
75
                } else {
76
                        $annotations = [];
×
77
                }
78

79
                // @phpstan-ignore-next-line
80
                if ($r instanceof ReflectionMethod && !$r->isPrivate() && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0]))
1✔
81
                ) {
82
                        try {
83
                                $inherited = self::getAll(new ReflectionMethod((string) get_parent_class($type), $member));
1✔
84
                        } catch (ReflectionException $e) {
1✔
85
                                try {
86
                                        $inherited = self::getAll($r->getPrototype());
1✔
87
                                } catch (ReflectionException $e) {
1✔
88
                                        $inherited = [];
1✔
89
                                }
90
                        }
91

92
                        $annotations += array_intersect_key($inherited, array_flip(self::$inherited));
1✔
93
                }
94

95
                return self::$cache[$type][$member] = $annotations;
1✔
96
        }
97

98
        /**
99
         * @return array<mixed>
100
         */
101
        private static function parseComment(string $comment): array
1✔
102
        {
103
                static $tokens = ['true' => true, 'false' => false, 'null' => null, '' => true];
1✔
104

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

109
                if ($parts === false) {
1✔
110
                        throw new LogicalException('Cannot split comment');
×
111
                }
112

113
                $description = trim($parts[0]);
1✔
114
                if ($description !== '') {
1✔
115
                        $res['description'] = [$description];
×
116
                }
117

118
                $matches = Strings::matchAll(
1✔
119
                        $parts[1] ?? '',
1✔
120
                        '~
121
                                (?<=\s|^)@(' . self::RE_IDENTIFIER . ')[ \t]*      ##  annotation
122
                                (
123
                                        \((?>' . self::RE_STRING . '|[^\'")@]+)+\)|  ##  (value)
1✔
124
                                        [^(@\r\n][^@\r\n]*|)                     ##  value
125
                        ~xi'
126
                );
127

128
                foreach ($matches as $match) {
1✔
129
                        [, $name, $value] = $match;
1✔
130

131
                        if (substr($value, 0, 1) === '(') {
1✔
132
                                $items = [];
1✔
133
                                $key = '';
1✔
134
                                $val = true;
1✔
135
                                $value[0] = ',';
1✔
136
                                while ($m = Strings::match($value, '#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')) {
1✔
137
                                        $value = substr($value, strlen($m[0]));
1✔
138
                                        [, $key, $val] = $m;
1✔
139
                                        $val = rtrim($val);
1✔
140
                                        if ($val[0] === "'" || $val[0] === '"') {
1✔
141
                                                $val = substr($val, 1, -1);
×
142

143
                                        } elseif (is_numeric($val)) {
1✔
144
                                                $val = 1 * $val;
×
145

146
                                        } else {
147
                                                $lval = strtolower($val);
1✔
148
                                                $val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
1✔
149
                                        }
150

151
                                        if ($key === '') {
1✔
152
                                                $items[] = $val;
1✔
153

154
                                        } else {
155
                                                $items[$key] = $val;
×
156
                                        }
157
                                }
158

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

161
                        } else {
162
                                $value = trim($value);
1✔
163
                                if (is_numeric($value)) {
1✔
164
                                        $value = 1 * $value;
×
165

166
                                } else {
167
                                        $lval = strtolower($value);
1✔
168
                                        $value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
1✔
169
                                }
170
                        }
171

172
                        $res[$name][] = is_array($value) ? ArrayHash::from($value) : $value;
1✔
173
                }
174

175
                return $res;
1✔
176
        }
177

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