Skip to content

Commit f8d564d

Browse files
committed
feat(symfony): stop watch system provider/processor
1 parent b52187d commit f8d564d

File tree

4 files changed

+171
-0
lines changed

4 files changed

+171
-0
lines changed

src/Symfony/Bundle/ApiPlatformBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlResolverPass;
2323
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\GraphQlTypePass;
2424
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\MetadataAwareNameConverterPass;
25+
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\ProfilerPass;
2526
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\SerializerMappingLoaderPass;
2627
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestClientPass;
2728
use ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler\TestMercureHubPass;
@@ -56,5 +57,6 @@ public function build(ContainerBuilder $container): void
5657
$container->addCompilerPass(new TestMercureHubPass());
5758
$container->addCompilerPass(new AuthenticatorManagerPass());
5859
$container->addCompilerPass(new SerializerMappingLoaderPass());
60+
$container->addCompilerPass(new ProfilerPass());
5961
}
6062
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Symfony\Bundle\DependencyInjection\Compiler;
15+
16+
use ApiPlatform\State\ProcessorInterface;
17+
use ApiPlatform\State\ProviderInterface;
18+
use ApiPlatform\Symfony\Bundle\State\TraceableProcessor;
19+
use ApiPlatform\Symfony\Bundle\State\TraceableProvider;
20+
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
21+
use Symfony\Component\DependencyInjection\ContainerBuilder;
22+
use Symfony\Component\DependencyInjection\Reference;
23+
24+
final class ProfilerPass implements CompilerPassInterface
25+
{
26+
public function process(ContainerBuilder $container): void
27+
{
28+
if (!$container->has('debug.stopwatch')) {
29+
return;
30+
}
31+
32+
$this->decorateProviders($container);
33+
$this->decorateProcessors($container);
34+
}
35+
36+
private function decorateProviders(ContainerBuilder $container): void
37+
{
38+
foreach ($this->findServiceIds($container, ProviderInterface::class, TraceableProvider::class) as $providerId) {
39+
$decoratorId = $providerId.'.traceable';
40+
$container->register($decoratorId, TraceableProvider::class)
41+
->setDecoratedService($providerId, null, -\PHP_INT_MAX)
42+
->setArguments([
43+
new Reference($decoratorId.'.inner'),
44+
new Reference('debug.stopwatch'),
45+
$providerId,
46+
]);
47+
}
48+
}
49+
50+
private function decorateProcessors(ContainerBuilder $container): void
51+
{
52+
foreach ($this->findServiceIds($container, ProcessorInterface::class, TraceableProcessor::class) as $processorId) {
53+
$decoratorId = $processorId.'.traceable';
54+
$container->register($decoratorId, TraceableProcessor::class)
55+
->setDecoratedService($processorId, null, -\PHP_INT_MAX)
56+
->setArguments([
57+
new Reference($decoratorId.'.inner'),
58+
new Reference('debug.stopwatch'),
59+
$processorId,
60+
]);
61+
}
62+
}
63+
64+
/**
65+
* @param class-string<object> $interface
66+
* @param class-string<object> $excludeClass
67+
*
68+
* @return string[]
69+
*/
70+
private function findServiceIds(ContainerBuilder $container, string $interface, string $excludeClass): array
71+
{
72+
$serviceIds = [];
73+
foreach (array_keys($container->getDefinitions()) as $id) {
74+
if (!$container->hasDefinition($id)) {
75+
continue;
76+
}
77+
78+
$definition = $container->getDefinition($id);
79+
if ($definition->isAbstract() || $definition->isSynthetic() || !$definition->getClass()) {
80+
continue;
81+
}
82+
83+
if (is_a($definition->getClass(), $excludeClass, true)) {
84+
continue;
85+
}
86+
87+
try {
88+
$class = $container->getParameterBag()->resolveValue($definition->getClass());
89+
if (!$class || (!class_exists($class) && !interface_exists($class))) {
90+
continue;
91+
}
92+
$reflectionClass = new \ReflectionClass($class);
93+
if ($reflectionClass->implementsInterface($interface)) {
94+
$serviceIds[] = $id;
95+
}
96+
} catch (\ReflectionException) {
97+
// ignore
98+
}
99+
}
100+
101+
return $serviceIds;
102+
}
103+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Symfony\Bundle\State;
15+
16+
use ApiPlatform\State\ProcessorInterface;
17+
use Symfony\Component\Stopwatch\Stopwatch;
18+
19+
final class TraceableProcessor implements ProcessorInterface
20+
{
21+
public function __construct(private readonly ProcessorInterface $processor, private readonly Stopwatch $stopwatch, private readonly string $name)
22+
{
23+
}
24+
25+
public function process(mixed ...$args): mixed
26+
{
27+
$this->stopwatch->start($this->name);
28+
$result = $this->processor->process(...$args);
29+
$this->stopwatch->stop($this->name);
30+
31+
return $result;
32+
}
33+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the API Platform project.
5+
*
6+
* (c) Kévin Dunglas <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
declare(strict_types=1);
13+
14+
namespace ApiPlatform\Symfony\Bundle\State;
15+
16+
use ApiPlatform\State\ProviderInterface;
17+
use Symfony\Component\Stopwatch\Stopwatch;
18+
19+
final class TraceableProvider implements ProviderInterface
20+
{
21+
public function __construct(private readonly ProviderInterface $provider, private readonly Stopwatch $stopwatch, private readonly string $name)
22+
{
23+
}
24+
25+
public function provide(mixed ...$args): object|array|null
26+
{
27+
$this->stopwatch->start($this->name);
28+
$result = $this->provider->provide(...$args);
29+
$this->stopwatch->stop($this->name);
30+
31+
return $result;
32+
}
33+
}

0 commit comments

Comments
 (0)