8000 [HttpKernel] Added support for timings in ArgumentValueResolvers · symfony/symfony@1c0d892 · GitHub
[go: up one dir, main page]

Skip to content
Sign in

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1c0d892

Browse files
Iltar van der Bergfabpot
Iltar van der Berg
authored andcommitted
[HttpKernel] Added support for timings in ArgumentValueResolvers
1 parent 368a0c3 commit 1c0d892

File tree

5 files changed

+200
-1
lines changed

5 files changed

+200
-1
lines changed

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ CHANGELOG
77
* added orphaned events support to `EventDataCollector`
88
* `ExceptionListener` now logs and collects exceptions at priority `2048` (previously logged at `-128` and collected at `0`)
99
* Deprecated `service:action` syntax with a single colon to reference controllers. Use `service::method` instead.
10+
* Added the ability to profile individual argument value resolvers via the
11+
`Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver`
1012

1113
4.0.0
1214
-----
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
namespace Symfony\Component\HttpKernel\Controller\ArgumentResolver;
13+
14+
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
16+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
17+
use Symfony\Component\Stopwatch\Stopwatch;
18+
19+
/**
20+
* Provides timing information via the stopwatch.
21+
*
22+
* @author Iltar van der Berg <kjarli@gmail.com>
23+
*/
24+
final class TraceableValueResolver implements ArgumentValueResolverInterface
25+
{
26+
private $inner;
27+
private $stopwatch;
28+
29+
public function __construct(ArgumentValueResolverInterface $inner, ?Stopwatch $stopwatch = null)
30+
{
31+
$this->inner = $inner;
32+
$this->stopwatch = $stopwatch ?? new Stopwatch();
33+
}
34+
35+
/**
36+
* {@inheritdoc}
37+
*/
38+
public function supports(Request $request, ArgumentMetadata $argument): bool
39+
{
40+
$method = \get_class($this->inner).'::'.__FUNCTION__;
41+
$this->stopwatch->start($method);
42+
43+
$return = $this->inner->supports($request, $argument);
44+
45+
$this->stopwatch->stop($method);
46+
47+
return $return;
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function resolve(Request $request, ArgumentMetadata $argument): iterable
54+
{
55+
$method = \get_class($this->inner).'::'.__FUNCTION__;
56+
$this->stopwatch->start($method);
57+
58+
yield from $this->inner->resolve($request, $argument);
59+
60+
$this->stopwatch->stop($method);
61+
}
62+
}

src/Symfony/Component/HttpKernel/DependencyInjection/ControllerArgumentValueResolverPass.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
1616
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
1717
use Symfony\Component\DependencyInjection\ContainerBuilder;
18+
use Symfony\Component\DependencyInjection\ContainerInterface;
19+
use Symfony\Component\DependencyInjection\Reference;
20+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver;
21+
use Symfony\Component\Stopwatch\Stopwatch;
1822

1923
/**
2024
* Gathers and configures the argument value resolvers.
@@ -40,9 +44,20 @@ public function process(ContainerBuilder $container)
4044
return;
4145
}
4246

47+
$resolvers = $this->findAndSortTaggedServices($this->argumentValueResolverTag, $container);
48+
49+
if ($container->getParameter('kernel.debug') && class_exists(Stopwatch::class)) {
50+
foreach ($resolvers as $resolverReference) {
51+
$id = (string) $resolverReference;
52+
$container->register("debug.$id", TraceableValueResolver::class)
53+
->setDecoratedService($id)
54+
->setArguments(array(new Reference("debug.$id.inner"), new Reference('debug.stopwatch', ContainerInterface::NULL_ON_INVALID_REFERENCE)));
55+
}
56+
}
57+
4358
$container
4459
->getDefinition($this->argumentResolverService)
45-
->replaceArgument(1, new IteratorArgument($this->findAndSortTaggedServices($this->argumentValueResolverTag, $container)))
60+
->replaceArgument(1, new IteratorArgument($resolvers))
4661
;
4762
}
4863
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
10000
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\HttpKernel\Tests\Controller\ArgumentResolver;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver;
17+
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
18+
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
19+
use Symfony\Component\Stopwatch\Stopwatch;
20+
21+
class TraceableValueResolverTest extends TestCase
22+
{
23+
public function testTimingsInSupports()
24+
{
25+
$stopwatch = new Stopwatch();
26+
$resolver = new TraceableValueResolver(new ResolverStub(), $stopwatch);
27+
$argument = new ArgumentMetadata('dummy', 'string', false, false, null);
28+
$request = new Request();
29+
30+
$this->assertTrue($resolver->supports($request, $argument));
31+
32+
$event = $stopwatch->getEvent(ResolverStub::class.'::supports');
33+
$this->assertCount(1, $event->getPeriods());
34+
}
35+
36+
public function testTimingsInResolve()
37+
{
38+
$stopwatch = new Stopwatch();
39+
$resolver = new TraceableValueResolver(new ResolverStub(), $stopwatch);
40+
$argument = new ArgumentMetadata('dummy', 'string', false, false, null);
41+
$request = new Request();
42+
43+
$iterable = $resolver->resolve($request, $argument);
44+
45+
foreach ($iterable as $index => $resolved) {
46+
$event = $stopwatch->getEvent(ResolverStub::class.'::resolve');
47+
$this->assertTrue($event->isStarted());
48+
$this->assertEmpty($event->getPeriods());
49+
switch ($index) {
50+
case 0:
51+
$this->assertEquals('first', $resolved);
52+
break;
53+
case 1:
54+
$this->assertEquals('second', $resolved);
55+
break;
56+
}
57+
}
58+
59+
$event = $stopwatch->getEvent(ResolverStub::class.'::resolve');
60+
$this->assertCount(1, $event->getPeriods());
61+
}
62+
}
63+
64+
class ResolverStub implements ArgumentValueResolverInterface
65+
{
66+
public function supports(Request $request, ArgumentMetadata $argument)
67+
{
68+
return true;
69+
}
70+
71+
public function resolve(Request $request, ArgumentMetadata $argument)
72+
{
73+
yield 'first';
74+
yield 'second';
75+
}
76+
}

src/Symfony/Component/HttpKernel/Tests/DependencyInjection/ControllerArgumentValueResolverPassTest.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,50 @@ public function testServicesAreOrderedAccordingToPriority()
4242
$container->register($id)->addTag('controller.argument_value_resolver', $tag);
4343
}
4444

45+
$container->setParameter('kernel.debug', false);
46+
4547
(new ControllerArgumentValueResolverPass())->process($container);
4648
$this->assertEquals($expected, $definition->getArgument(1)->getValues());
49+
50+
$this->assertFalse($container->hasDefinition('n1.traceable'));
51+
$this->assertFalse($container->hasDefinition('n2.traceable'));
52+
$this->assertFalse($container->hasDefinition('n3.traceable'));
53+
}
54+
55+
public function testInDebug()
56+
{
57+
$services = array(
58+
'n3' => array(array()),
59+
'n1' => array(array('priority' => 200)),
60+
'n2' => array(array('priority' => 100)),
61+
);
62+
63+
$expected = array(
64+
new Reference('n1'),
65+
new Reference('n2'),
66+
new Reference('n3'),
67+
);
68+
69+
$definition = new Definition(ArgumentResolver::class, array(null, array()));
70+
$container = new ContainerBuilder();
71+
$container->setDefinition('argument_resolver', $definition);
72+
73+
foreach ($services as $id => list($tag)) {
74+
$container->register($id)->addTag('controller.argument_value_resolver', $tag);
75+
}
76+
77+
$container->setParameter('kernel.debug', true);
78+
79+
(new ControllerArgumentValueResolverPass())->process($container);
80+
$this->assertEquals($expected, $definition->getArgument(1)->getValues());
81+
82+
$this->assertTrue($container->hasDefinition('debug.n1'));
83+
$this->assertTrue($container->hasDefinition('debug.n2'));
84+
$this->assertTrue($container->hasDefinition('debug.n3'));
85+
86+
$this->assertTrue($container->hasDefinition('n1'));
87+
$this->assertTrue($container->hasDefinition('n2'));
88+
$this->assertTrue($container->hasDefinition('n3'));
4789
}
4890

4991
public function testReturningEmptyArrayWhenNoService()
@@ -52,6 +94,8 @@ public function testReturningEmptyArrayWhenNoService()
5294
$container = new ContainerBuilder();
5395
$container->setDefinition('argument_resolver', $definition);
5496

97+
$container->setParameter('kernel.debug', false);
98+
5599
(new ControllerArgumentValueResolverPass())->process($container);
56100
$this->assertEquals(array(), $definition->getArgument(1)->getValues());
57101
}

0 commit comments

Comments
 (0)
0