Skip to content

Commit c7e4532

Browse files
committed
minor #2201 [TwigComponent] Optimize ComponentFactory (smnandre)
This PR was squashed before being merged into the 2.x branch. Discussion ---------- [TwigComponent] Optimize ComponentFactory | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | Issues | Fix #... | License | MIT Some internal refactor focused on the Component Factory / Anonymous components usage. * Optimize the hot path * Store anonymous template resolution * Avoid anonymous checks for class-based components * Add ComponentFactory unit tests * Add ComponentMetadata::isAnonymous() method * Fix loop * Reuse metadata to instanciate and mount component Other PRs will follow :) Commits ------- da0537d [TwigComponent] Optimize ComponentFactory
2 parents 06e026c + da0537d commit c7e4532

File tree

3 files changed

+131
-39
lines changed

3 files changed

+131
-39
lines changed

src/TwigComponent/src/ComponentFactory.php

+34-39
Original file line numberDiff line numberDiff line change
@@ -41,32 +41,42 @@ public function __construct(
4141

4242
public function metadataFor(string $name): ComponentMetadata
4343
{
44-
$name = $this->classMap[$name] ?? $name;
45-
46-
if (!$config = $this->config[$name] ?? null) {
47-
if (($template = $this->componentTemplateFinder->findAnonymousComponentTemplate($name)) !== null) {
48-
return new ComponentMetadata([
49-
'key' => $name,
50-
'template' => $template,
51-
]);
44+
if ($config = $this->config[$name] ?? null) {
45+
return new ComponentMetadata($config);
46+
}
47+
48+
if ($template = $this->componentTemplateFinder->findAnonymousComponentTemplate($name)) {
49+
$this->config[$name] = [
50+
'key' => $name,
51+
'template' => $template,
52+
];
53+
54+
return new ComponentMetadata($this->config[$name]);
55+
}
56+
57+
if ($mappedName = $this->classMap[$name] ?? null) {
58+
if ($config = $this->config[$mappedName] ?? null) {
59+
return new ComponentMetadata($config);
5260
}
5361

54-
$this->throwUnknownComponentException($name);
62+
throw new \InvalidArgumentException(\sprintf('Unknown component "%s".', $name));
5563
}
5664

57-
return new ComponentMetadata($config);
65+
$this->throwUnknownComponentException($name);
5866
}
5967

6068
/**
6169
* Creates the component and "mounts" it with the passed data.
6270
*/
6371
public function create(string $name, array $data = []): MountedComponent
6472
{
65-
return $this->mountFromObject(
66-
$this->getComponent($name),
67-
$data,
68-
$this->metadataFor($name)
69-
);
73+
$metadata = $this->metadataFor($name);
74+
75+
if ($metadata->isAnonymous()) {
76+
return $this->mountFromObject(new AnonymousComponent(), $data, $metadata);
77+
}
78+
79+
return $this->mountFromObject($this->components->get($metadata->getName()), $data, $metadata);
7080
}
7181

7282
/**
@@ -101,10 +111,7 @@ public function mountFromObject(object $component, array $data, ComponentMetadat
101111
foreach ($data as $key => $value) {
102112
if ($value instanceof \Stringable) {
103113
$data[$key] = (string) $value;
104-
continue;
105114
}
106-
107-
$data[$key] = $value;
108115
}
109116

110117
return new MountedComponent(
@@ -118,10 +125,18 @@ public function mountFromObject(object $component, array $data, ComponentMetadat
118125

119126
/**
120127
* Returns the "unmounted" component.
128+
*
129+
* @internal
121130
*/
122131
public function get(string $name): object
123132
{
124-
return $this->getComponent($name);
133+
$metadata = $this->metadataFor($name);
134+
135+
if ($metadata->isAnonymous()) {
136+
return new AnonymousComponent();
137+
}
138+
139+
return $this->components->get($metadata->getName());
125140
}
126141

127142
private function mount(object $component, array &$data): void
@@ -159,21 +174,6 @@ private function mount(object $component, array &$data): void
159174
$component->mount(...$parameters);
160175
}
161176

162-
private function getComponent(string $name): object
163-
{
164-
$name = $this->classMap[$name] ?? $name;
165-
166-
if (!$this->components->has($name)) {
167-
if ($this->isAnonymousComponent($name)) {
168-
return new AnonymousComponent();
169-
}
170-
171-
$this->throwUnknownComponentException($name);
172-
}
173-
174-
return $this->components->get($name);
175-
}
176-
177177
private function preMount(object $component, array $data, ComponentMetadata $componentMetadata): array
178178
{
179179
$event = new PreMountEvent($component, $data, $componentMetadata);
@@ -215,11 +215,6 @@ private function postMount(object $component, array $data, ComponentMetadata $co
215215
];
216216
}
217217

218-
private function isAnonymousComponent(string $name): bool
219-
{
220-
return null !== $this->componentTemplateFinder->findAnonymousComponentTemplate($name);
221-
}
222-
223218
/**
224219
* @return never
225220
*/

src/TwigComponent/src/ComponentMetadata.php

+5
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,11 @@ public function isPublicPropsExposed(): bool
5757
return $this->get('expose_public_props', false);
5858
}
5959

60+
public function isAnonymous(): bool
61+
{
62+
return !isset($this->config['service_id']);
63+
}
64+
6065
public function getAttributesVar(): string
6166
{
6267
return $this->get('attributes_var', 'attributes');
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[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+
namespace Symfony\UX\TwigComponent\Tests\Unit;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\DependencyInjection\ServiceLocator;
16+
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
17+
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
18+
use Symfony\UX\TwigComponent\ComponentFactory;
19+
use Symfony\UX\TwigComponent\ComponentTemplateFinderInterface;
20+
21+
/**
22+
* @author Simon André <[email protected]>
23+
*/
24+
class ComponentFactoryTest extends TestCase
25+
{
26+
public function testMetadataForConfig(): void
27+
{
28+
$factory = new ComponentFactory(
29+
$this->createMock(ComponentTemplateFinderInterface::class),
30+
$this->createMock(ServiceLocator::class),
31+
$this->createMock(PropertyAccessorInterface::class),
32+
$this->createMock(EventDispatcherInterface::class),
33+
['foo' => ['key' => 'foo', 'template' => 'bar.html.twig']],
34+
[]
35+
);
36+
37+
$metadata = $factory->metadataFor('foo');
38+
39+
$this->assertSame('foo', $metadata->getName());
40+
$this->assertSame('bar.html.twig', $metadata->getTemplate());
41+
}
42+
43+
public function testMetadataForResolveAlias(): void
44+
{
45+
$factory = new ComponentFactory(
46+
$this->createMock(ComponentTemplateFinderInterface::class),
47+
$this->createMock(ServiceLocator::class),
48+
$this->createMock(PropertyAccessorInterface::class),
49+
$this->createMock(EventDispatcherInterface::class),
50+
[
51+
'bar' => ['key' => 'bar', 'template' => 'bar.html.twig'],
52+
'foo' => ['key' => 'foo', 'template' => 'foo.html.twig'],
53+
],
54+
['Foo\\Bar' => 'bar'],
55+
);
56+
57+
$metadata = $factory->metadataFor('Foo\\Bar');
58+
59+
$this->assertSame('bar', $metadata->getName());
60+
$this->assertSame('bar.html.twig', $metadata->getTemplate());
61+
}
62+
63+
public function testMetadataForReuseAnonymousConfig(): void
64+
{
65+
$templateFinder = $this->createMock(ComponentTemplateFinderInterface::class);
66+
$templateFinder->expects($this->atLeastOnce())
67+
->method('findAnonymousComponentTemplate')
68+
->with('foo')
69+
->willReturnOnConsecutiveCalls('foo.html.twig', 'bar.html.twig', 'bar.html.twig');
70+
71+
$factory = new ComponentFactory(
72+
$templateFinder,
73+
$this->createMock(ServiceLocator::class),
74+
$this->createMock(PropertyAccessorInterface::class),
75+
$this->createMock(EventDispatcherInterface::class),
76+
[],
77+
[]
78+
);
79+
80+
$metadata = $factory->metadataFor('foo');
81+
$this->assertSame('foo', $metadata->getName());
82+
$this->assertSame('foo.html.twig', $metadata->getTemplate());
83+
84+
$metadata = $factory->metadataFor('foo');
85+
$this->assertSame('foo', $metadata->getName());
86+
$this->assertSame('foo.html.twig', $metadata->getTemplate());
87+
88+
$metadata = $factory->metadataFor('foo');
89+
$this->assertSame('foo', $metadata->getName());
90+
$this->assertSame('foo.html.twig', $metadata->getTemplate());
91+
}
92+
}

0 commit comments

Comments
 (0)