Skip to content

Commit b90e7fd

Browse files
committed
Refactor: moved annotation parse from contributte/utils to this pkg
1 parent 6e68a47 commit b90e7fd

3 files changed

Lines changed: 196 additions & 2 deletions

File tree

composer.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
],
2020
"require": {
2121
"php": ">=8.1",
22-
"contributte/utils": "^0.6 || ^0.7",
2322
"nette/di": "^3.1.8",
2423
"nextras/orm": "^4.0.7"
2524
},

src/DI/NextrasOrmEventsExtension.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use Contributte\Nextras\Orm\Events\Listeners\BeforeRemoveListener;
1212
use Contributte\Nextras\Orm\Events\Listeners\BeforeUpdateListener;
1313
use Contributte\Nextras\Orm\Events\Listeners\FlushListener;
14-
use Contributte\Utils\Annotations;
14+
use Contributte\Nextras\Orm\Events\Utils\Annotations;
1515
use Nette\DI\CompilerExtension;
1616
use Nette\DI\Definitions\ServiceDefinition;
1717
use Nette\DI\ServiceCreationException;

src/Utils/Annotations.php

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace Contributte\Nextras\Orm\Events\Utils;
4+
5+
use LogicException;
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
44+
{
45+
return self::getAnnotation($r, $name) !== null;
46+
}
47+
48+
/**
49+
* @param ReflectionClass<object>|ReflectionMethod|ReflectionProperty|ReflectionFunction $r
50+
*/
51+
public static function getAnnotation(Reflector $r, string $name): mixed
52+
{
53+
$res = self::getAnnotations($r);
54+
55+
if ($res === []) {
56+
return null;
57+
}
58+
59+
return isset($res[$name]) ? end($res[$name]) : null;
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
67+
{
68+
if ($r instanceof ReflectionClass) {
69+
$type = $r->getName();
70+
$member = 'class';
71+
72+
} elseif ($r instanceof ReflectionMethod) {
73+
$type = $r->getDeclaringClass()->getName();
74+
$member = $r->getName();
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
86+
self::$useReflection = (bool) (new ReflectionClass(self::class))->getDocComment();
87+
}
88+
89+
if (isset(self::$cache[$type][$member])) { // is value cached?
90+
return self::$cache[$type][$member];
91+
}
92+
93+
$annotations = self::$useReflection ? self::parseComment((string) $r->getDocComment()) : [];
94+
95+
// @phpstan-ignore-next-line
96+
if ($r instanceof ReflectionMethod && !$r->isPrivate() && (!$r->isConstructor() || !empty($annotations['inheritdoc'][0]))
97+
) {
98+
try {
99+
// @phpstan-ignore-next-line
100+
$inherited = self::getAnnotations(new ReflectionMethod((string) get_parent_class($type), $member));
101+
} catch (ReflectionException $e) {
102+
try {
103+
$inherited = self::getAnnotations($r->getPrototype());
104+
} catch (ReflectionException $e) {
105+
$inherited = [];
106+
}
107+
}
108+
109+
$annotations += array_intersect_key($inherited, array_flip(self::$inherited));
110+
}
111+
112+
return self::$cache[$type][$member] = $annotations;
113+
}
114+
115+
/**
116+
* @return array<array<string|int,mixed[]>>
117+
*/
118+
private static function parseComment(string $comment): array
119+
{
120+
static $tokens = ['true' => true, 'false' => false, 'null' => null, '' => true];
121+
122+
$res = [];
123+
$comment = (string) preg_replace('#^\s*\*\s?#ms', '', trim($comment, '/*'));
124+
$parts = preg_split('#^\s*(?=@' . self::RE_IDENTIFIER . ')#m', $comment, 2);
125+
126+
if ($parts === false) {
127+
throw new LogicException('Cannot split comment');
128+
}
129+
130+
$description = trim($parts[0]);
131+
if ($description !== '') {
132+
$res['description'] = [$description];
133+
}
134+
135+
$matches = Strings::matchAll(
136+
$parts[1] ?? '',
137+
'~
138+
(?<=\s|^)@(' . self::RE_IDENTIFIER . ')[ \t]* ## annotation
139+
(
140+
\((?>' . self::RE_STRING . '|[^\'")@]+)+\)| ## (value)
141+
[^(@\r\n][^@\r\n]*|) ## value
142+
~xi'
143+
);
144+
145+
foreach ($matches as $match) {
146+
[, $name, $value] = $match;
147+
148+
if (substr($value, 0, 1) === '(') {
149+
$items = [];
150+
$key = '';
151+
$val = true;
152+
$value[0] = ',';
153+
while ($m = Strings::match($value, '#\s*,\s*(?>(' . self::RE_IDENTIFIER . ')\s*=\s*)?(' . self::RE_STRING . '|[^\'"),\s][^\'"),]*)#A')) {
154+
$value = substr($value, strlen($m[0]));
155+
[, $key, $val] = $m;
156+
$val = rtrim($val);
157+
if ($val[0] === "'" || $val[0] === '"') {
158+
$val = substr($val, 1, -1);
159+
160+
} elseif (is_numeric($val)) {
161+
$val = 1 * $val;
162+
163+
} else {
164+
$lval = strtolower($val);
165+
$val = array_key_exists($lval, $tokens) ? $tokens[$lval] : $val;
166+
}
167+
168+
if ($key === '') {
169+
$items[] = $val;
170+
171+
} else {
172+
$items[$key] = $val;
173+
}
174+
}
175+
176+
$value = count($items) < 2 && $key === '' ? $val : $items;
177+
178+
} else {
179+
$value = trim($value);
180+
if (is_numeric($value)) {
181+
$value = 1 * $value;
182+
183+
} else {
184+
$lval = strtolower($value);
185+
$value = array_key_exists($lval, $tokens) ? $tokens[$lval] : $value;
186+
}
187+
}
188+
189+
$res[$name][] = is_array($value) ? ArrayHash::from($value) : $value;
190+
}
191+
192+
return $res;
193+
}
194+
195+
}

0 commit comments

Comments
 (0)