Skip to content

Commit 0e9024c

Browse files
Merge pull request #3 from biurad/performance
Performance Update
2 parents 0834ff5 + bb6654d commit 0e9024c

File tree

12 files changed

+180
-185
lines changed

12 files changed

+180
-185
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/.idea/
22
/.vscode/
33
/.phpcs-cache
4-
/.php_cs.dist
4+
/.php_cs.dist.php
55
/.phpunit.result.cache
66
/build/
77
composer.phar

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ foreach ($$listeners as $collector) {
5151
}
5252
```
5353

54+
> **NB:** If you are on PHP 8 and wishes to use attributes only, please avoid using `spiral/attributes` package for best performance. Contributing why this library is not shipped with `spiral/attributes` package.
55+
5456
## 📓 Documentation
5557

5658
For in-depth documentation before using this library. Full documentation on advanced usage, configuration, and customization can be found at [docs.biurad.com][docs].

composer.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
},
2424
"require": {
2525
"php": "^7.2 || ^8.0",
26-
"spiral/attributes": "dev-master"
26+
"symfony/polyfill-php80": "^1.22"
2727
},
2828
"extra": {
2929
"branch-alias": {
@@ -36,7 +36,8 @@
3636
"phpstan/phpstan": "^0.12",
3737
"phpstan/phpstan-strict-rules": "^0.12",
3838
"phpunit/phpunit": "^8.5 || ^9.5",
39-
"squizlabs/php_codesniffer": "^3.5",
39+
"spiral/attributes": "dev-master",
40+
"squizlabs/php_codesniffer": "^3.6",
4041
"vimeo/psalm": "^4.7"
4142
},
4243
"autoload": {
@@ -62,7 +63,8 @@
6263
]
6364
},
6465
"suggest": {
65-
"doctrine/annotations": "^1.8 for Doctrine metadata driver support"
66+
"doctrine/annotations": "^1.8 for Doctrine metadata driver support",
67+
"spiral/attributes": "For loading doctrine and/or PHP 8 attributes for PHP 7"
6668
},
6769
"minimum-stability": "dev",
6870
"prefer-stable": true

phpstan.neon.dist

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,7 @@ parameters:
55
level: 2
66
paths: [src]
77
checkGenericClassInNonGenericObjectType: false
8+
9+
ignoreErrors:
10+
- "#^Construct empty\\(\\) is not allowed. Use more strict comparison.$#"
11+
- "#^Call to an undefined method Reflector\\:\\:getAttributes\\(\\).|Parameter \\$attribute of anonymous function has invalid typehint type ReflectionAttribute.|Call to method newInstance\\(\\) on an unknown class ReflectionAttribute.$#"

psalm.xml.dist

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@
2121
</projectFiles>
2222

2323
<issueHandlers>
24-
<MethodSignatureMismatch>
24+
<UndefinedClass>
2525
<errorLevel type="suppress">
26-
<directory name="src/Locate"/>
26+
<referencedClass name="ReflectionAttribute"/>
2727
</errorLevel>
28-
</MethodSignatureMismatch>
28+
</UndefinedClass>
2929
</issueHandlers>
3030
</psalm>

src/AnnotationLoader.php

Lines changed: 81 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@
2626
*/
2727
class AnnotationLoader implements LoaderInterface
2828
{
29-
/** @var ReaderInterface */
29+
/** @var ReaderInterface|null */
3030
private $reader;
3131

3232
/** @var mixed[] */
3333
private $annotations;
3434

35-
/** @var ListenerInterface[] */
35+
/** @var array<string,ListenerInterface> */
3636
private $listeners = [];
3737

3838
/** @var string[] */
@@ -44,8 +44,12 @@ class AnnotationLoader implements LoaderInterface
4444
/**
4545
* @param callable $classLoader
4646
*/
47-
public function __construct(ReaderInterface $reader, callable $classLoader = null)
47+
public function __construct(ReaderInterface $reader = null, callable $classLoader = null)
4848
{
49+
if (\PHP_VERSION_ID < 80000 && null === $reader) {
50+
throw new \RuntimeException(\sprintf('A "%s" instance to read annotations/attributes not available.', ReaderInterface::class));
51+
}
52+
4953
$this->reader = $reader;
5054
$this->classLoader = $classLoader;
5155
}
@@ -55,119 +59,118 @@ public function __construct(ReaderInterface $reader, callable $classLoader = nul
5559
*/
5660
public function listener(ListenerInterface ...$listeners): void
5761
{
58-
$this->listeners += $listeners;
62+
foreach ($listeners as $listener) {
63+
$this->listeners[$listener->getAnnotation()] = $listener;
64+
}
5965
}
6066

6167
/**
6268
* {@inheritdoc}
6369
*/
6470
public function resource(string ...$resources): void
6571
{
66-
$this->resources += $resources;
72+
foreach ($resources as $resource) {
73+
$this->resources[] = $resource;
74+
}
6775
}
6876

6977
/**
7078
* {@inheritdoc}
7179
*/
72-
public function build(): void
80+
public function build(?string ...$annotationClass): void
7381
{
74-
$this->annotations = $annotations = $classes = $files = [];
82+
$this->annotations = $annotations = $files = [];
83+
84+
if (1 === \count($annotationClass = \array_merge($annotationClass, \array_keys($this->listeners)))) {
85+
$annotationClass = $annotationClass[0];
86+
}
7587

7688
foreach ($this->resources as $resource) {
7789
if (\is_dir($resource)) {
7890
$files += $this->findFiles($resource);
79-
80-
continue;
81-
}
82-
83-
if (!(\class_exists($resource) || \function_exists($resource))) {
84-
continue;
91+
} elseif (\function_exists($resource) || \class_exists($resource)) {
92+
$annotations = \array_replace_recursive($annotations, $this->findAnnotations($resource, $annotationClass));
8593
}
86-
87-
$classes[] = $resource;
8894
}
8995

90-
$classes += $this->findClasses($files);
91-
92-
foreach ($classes as $class) {
93-
$annotations[] = $this->findAnnotations($class);
96+
if (!empty($files)) {
97+
foreach ($this->findClasses($files) as $class) {
98+
$annotations = \array_replace_recursive($annotations, $this->findAnnotations($class, $annotationClass));
99+
}
94100
}
95101

96-
foreach ($this->listeners as $listener) {
97-
$listenerAnnotations = [];
102+
foreach ((array) $annotationClass as $annotation) {
103+
$loadedAnnotation = \array_filter($annotations[$annotation] ?? []);
98104

99-
foreach ($annotations as $annotation) {
100-
if (isset($annotation[$listener->getAnnotation()])) {
101-
$listenerAnnotations[] = $annotation[$listener->getAnnotation()];
102-
}
105+
if (isset($this->listeners[$annotation])) {
106+
$loadedAnnotation = $this->listeners[$annotation]->load($loadedAnnotation);
103107
}
104108

105-
$found = $listener->load($listenerAnnotations);
106-
107-
if (null !== $found) {
108-
$this->annotations[] = $found;
109-
}
109+
$this->annotations[$annotation] = $loadedAnnotation;
110110
}
111-
112-
\gc_mem_caches();
113111
}
114112

115113
/**
116114
* {@inheritdoc}
117115
*/
118-
public function load(): iterable
116+
public function load(string $annotationClass = null, bool $stale = true)
119117
{
120-
if (null === $this->annotations) {
121-
$this->build();
118+
if (!$stale || null === $this->annotations) {
119+
$this->build($annotationClass);
122120
}
123121

124-
return $this->annotations;
122+
if (isset($annotationClass, $this->annotations[$annotationClass])) {
123+
return $this->annotations[$annotationClass] ?? null;
124+
}
125+
126+
return \array_filter($this->annotations);
125127
}
126128

127129
/**
128130
* Finds annotations in the given resource.
129131
*
130132
* @param class-string|string $resource
133+
* @param string[]|string $annotationClass
131134
*
132135
* @return Locate\Class_[]|Locate\Function_[]
133136
*/
134-
private function findAnnotations(string $resource)
137+
private function findAnnotations(string $resource, $annotationClass): iterable
135138
{
136-
$annotations = [];
137-
138-
foreach ($this->listeners as $listener) {
139-
$annotationClass = $listener->getAnnotation();
140-
141-
if (\function_exists($resource)) {
142-
$funcReflection = new \ReflectionFunction($resource);
143-
$function = $this->fetchFunctionAnnotation($funcReflection, $this->getAnnotations($funcReflection, $annotationClass), $annotationClass);
139+
if (empty($annotationClass)) {
140+
return [];
141+
}
144142

145-
if (null !== $function) {
146-
$annotations[$annotationClass] = $function;
147-
}
143+
if (\is_array($annotationClass)) {
144+
$annotations = [];
148145

149-
continue;
146+
foreach ($annotationClass as $annotation) {
147+
$annotations = \array_replace_recursive($annotations, $this->findAnnotations($resource, $annotation));
150148
}
151149

152-
$classReflection = new \ReflectionClass($resource);
150+
return $annotations;
151+
}
153152

154-
if ($classReflection->isAbstract()) {
155-
continue;
156-
}
153+
if (\function_exists($resource)) {
154+
$funcReflection = new \ReflectionFunction($resource);
155+
$annotation = $this->fetchFunctionAnnotation($funcReflection, $this->getAnnotations($funcReflection, $annotationClass), $annotationClass);
157156

158-
$annotation = new Locate\Class_($this->getAnnotations($classReflection, $annotationClass), $classReflection);
157+
goto annotation;
158+
}
159159

160-
// Reflections belonging to class object.
161-
$reflections = \array_merge(
162-
$classReflection->getMethods(),
163-
$classReflection->getProperties(),
164-
$classReflection->getConstants()
165-
);
160+
$classReflection = new \ReflectionClass($resource);
166161

167-
$annotations[$annotationClass] = $this->fetchAnnotations($annotation, $reflections, $annotationClass);
162+
if ($classReflection->isAbstract()) {
163+
return [];
168164
}
169165

170-
return $annotations;
166+
$annotation = $this->fetchAnnotations(
167+
new Locate\Class_($this->getAnnotations($classReflection, $annotationClass), $classReflection),
168+
\array_merge($classReflection->getMethods(), $classReflection->getProperties(), $classReflection->getConstants()),
169+
$annotationClass
170+
);
171+
172+
annotation:
173+
return [$annotationClass => [$resource => $annotation]];
171174
}
172175

173176
/**
@@ -179,29 +182,22 @@ private function getAnnotations(\Reflector $reflection, string $annotation): ite
179182
{
180183
$annotations = [];
181184

182-
switch (true) {
183-
case $reflection instanceof \ReflectionClass:
184-
$annotations = $this->reader->getClassMetadata($reflection, $annotation);
185-
186-
break;
187-
188-
case $reflection instanceof \ReflectionFunctionAbstract:
189-
$annotations = $this->reader->getFunctionMetadata($reflection, $annotation);
190-
191-
break;
192-
193-
case $reflection instanceof \ReflectionProperty:
194-
$annotations = $this->reader->getPropertyMetadata($reflection, $annotation);
195-
196-
break;
197-
198-
case $reflection instanceof \ReflectionClassConstant:
199-
$annotations = $this->reader->getConstantMetadata($reflection, $annotation);
200-
201-
break;
185+
if (null === $this->reader) {
186+
return \array_map(static function (\ReflectionAttribute $attribute): object {
187+
return $attribute->newInstance();
188+
}, $reflection->getAttributes($annotation));
189+
}
202190

203-
case $reflection instanceof \ReflectionParameter:
204-
$annotations = $this->reader->getParameterMetadata($reflection, $annotation);
191+
if ($reflection instanceof \ReflectionClass) {
192+
$annotations = $this->reader->getClassMetadata($reflection, $annotation);
193+
} elseif ($reflection instanceof \ReflectionFunctionAbstract) {
194+
$annotations = $this->reader->getFunctionMetadata($reflection, $annotation);
195+
} elseif ($reflection instanceof \ReflectionProperty) {
196+
$annotations = $this->reader->getPropertyMetadata($reflection, $annotation);
197+
} elseif ($reflection instanceof \ReflectionClassConstant) {
198+
$annotations = $this->reader->getConstantMetadata($reflection, $annotation);
199+
} elseif ($reflection instanceof \ReflectionParameter) {
200+
$annotations = $this->reader->getParameterMetadata($reflection, $annotation);
205201
}
206202

207203
return $annotations instanceof \Generator ? \iterator_to_array($annotations) : $annotations;
@@ -286,7 +282,7 @@ private function fetchFunctionAnnotation(\ReflectionFunctionAbstract $reflection
286282
*
287283
* @param string[] $files
288284
*
289-
* @return class-string[]
285+
* @return string[]
290286
*/
291287
private function findClasses(array $files): array
292288
{

src/LoaderInterface.php

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,32 @@
2020
interface LoaderInterface
2121
{
2222
/**
23-
* Attache(s) the given resource(s) to the loader
23+
* Attache(s) the given resource(s) to the loader.
2424
*
25-
* @param string ...$resources type of class string, file, or directory
25+
* @param string ...$resources type of class string, function name, file, or directory
2626
*/
2727
public function resource(string ...$resources): void;
2828

2929
/**
30-
* Attache(s) the given listener(s) to the loader
30+
* Attache(s) the given listener(s) to the loader.
3131
*
3232
* @param ListenerInterface ...$listeners
3333
*/
3434
public function listener(ListenerInterface ...$listeners): void;
3535

3636
/**
37-
* Loads annotations from attached resources
37+
* Loads annotations from attached resources.
3838
*
39-
* @return iterable|mixed[]
39+
* @param bool $stale If true rebuilding annotations/attributes is locked
40+
*
41+
* @return mixed
4042
*/
41-
public function load(): iterable;
43+
public function load(string $annotationClass = null, bool $stale = true);
4244

4345
/**
44-
* This finds and build annotations once
46+
* This finds and build annotations once.
47+
*
48+
* @param string|null ...$annotationClass
4549
*/
46-
public function build(): void;
50+
public function build(?string ...$annotationClass): void;
4751
}

0 commit comments

Comments
 (0)