Skip to content

Commit 92e008e

Browse files
committed
[Twig] Store mount methods in compiler pass
1 parent 7dff0df commit 92e008e

File tree

16 files changed

+115
-176
lines changed

16 files changed

+115
-176
lines changed

src/TwigComponent/src/Attribute/AsTwigComponent.php

Lines changed: 4 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -75,53 +75,15 @@ public function serviceConfig(): array
7575
];
7676
}
7777

78-
/**
79-
* @param object|class-string $component
80-
*
81-
* @internal
82-
*/
83-
public static function mountMethod(object|string $component): ?\ReflectionMethod
84-
{
85-
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
86-
if ('mount' === $method->getName()) {
87-
return $method;
88-
}
89-
}
90-
91-
return null;
92-
}
93-
94-
/**
95-
* @param object|class-string $component
96-
*
97-
* @return \ReflectionMethod[]
98-
*
99-
* @internal
100-
*/
101-
public static function postMountMethods(object|string $component): array
102-
{
103-
return self::attributeMethodsByPriorityFor($component, PostMount::class);
104-
}
105-
106-
/**
107-
* @param object|class-string $component
108-
*
109-
* @return \ReflectionMethod[]
110-
*
111-
* @internal
112-
*/
113-
public static function preMountMethods(object|string $component): array
114-
{
115-
return self::attributeMethodsByPriorityFor($component, PreMount::class);
116-
}
117-
11878
/**
11979
* @param object|class-string $component
12080
* @param class-string $attributeClass
12181
*
12282
* @return \ReflectionMethod[]
12383
*
12484
* @internal
85+
*
86+
* @deprecated since 2.22 - to be removed in 3.0
12587
*/
12688
protected static function attributeMethodsByPriorityFor(object|string $component, string $attributeClass): array
12789
{
@@ -138,6 +100,8 @@ protected static function attributeMethodsByPriorityFor(object|string $component
138100
* @return \Traversable<\ReflectionMethod>
139101
*
140102
* @internal
103+
*
104+
* @deprecated since 2.22 - to be removed in 3.0
141105
*/
142106
protected static function attributeMethodsFor(string $attribute, object|string $component): \Traversable
143107
{

src/TwigComponent/src/Command/TwigComponentDebugCommand.php

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
use Symfony\Component\Console\Output\OutputInterface;
2222
use Symfony\Component\Console\Style\SymfonyStyle;
2323
use Symfony\Component\Finder\Finder;
24-
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
2524
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;
2625
use Symfony\UX\TwigComponent\ComponentFactory;
2726
use Symfony\UX\TwigComponent\ComponentMetadata;
@@ -214,7 +213,7 @@ private function displayComponentDetails(SymfonyStyle $io, string $name): void
214213
]);
215214

216215
// Anonymous Component
217-
if (null === $metadata->get('class')) {
216+
if ($metadata->isAnonymous()) {
218217
$table->addRows([
219218
['Type', '<comment>Anonymous</comment>'],
220219
new TableSeparator(),
@@ -229,7 +228,7 @@ private function displayComponentDetails(SymfonyStyle $io, string $name): void
229228
['Type', $metadata->get('live') ? '<info>Live</info>' : ''],
230229
new TableSeparator(),
231230
// ['Attributes Var', $metadata->get('attributes_var')],
232-
['Public Props', $metadata->get('expose_public_props') ? 'Yes' : 'No'],
231+
['Public Props', $metadata->isPublicPropsExposed() ? 'Yes' : 'No'],
233232
['Properties', implode("\n", $this->getComponentProperties($metadata))],
234233
]);
235234

@@ -242,14 +241,15 @@ private function displayComponentDetails(SymfonyStyle $io, string $name): void
242241
return \sprintf('%s(%s)', $m->getName(), implode(', ', $params));
243242
};
244243
$hooks = [];
245-
if ($method = AsTwigComponent::mountMethod($metadata->getClass())) {
246-
$hooks[] = ['Mount', $logMethod($method)];
244+
$reflector = new \ReflectionClass($metadata->getClass());
245+
foreach ($metadata->getPreMounts() as $method) {
246+
$hooks[] = ['PreMount', $logMethod($reflector->getMethod($method))];
247247
}
248-
foreach (AsTwigComponent::preMountMethods($metadata->getClass()) as $method) {
249-
$hooks[] = ['PreMount', $logMethod($method)];
248+
foreach ($metadata->getMounts() as $method) {
249+
$hooks[] = ['Mount', $logMethod($reflector->getMethod($method))];
250250
}
251-
foreach (AsTwigComponent::postMountMethods($metadata->getClass()) as $method) {
252-
$hooks[] = ['PostMount', $logMethod($method)];
251+
foreach ($metadata->getPostMounts() as $method) {
252+
$hooks[] = ['PostMount', $logMethod($reflector->getMethod($method))];
253253
}
254254
if ($hooks) {
255255
$table->addRows([

src/TwigComponent/src/ComponentFactory.php

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
use Symfony\Component\DependencyInjection\ServiceLocator;
1616
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
1717
use Symfony\Contracts\Service\ResetInterface;
18-
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
1918
use Symfony\UX\TwigComponent\Event\PostMountEvent;
2019
use Symfony\UX\TwigComponent\Event\PreMountEvent;
2120

@@ -26,13 +25,12 @@
2625
*/
2726
final class ComponentFactory implements ResetInterface
2827
{
29-
private static $mountMethods = [];
30-
private static $preMountMethods = [];
31-
private static $postMountMethods = [];
28+
private static array $mountMethods = [];
3229

3330
/**
3431
* @param array<string, array> $config
3532
* @param array<class-string, string> $classMap
33+
* @param array<class-string, array<string, string[]> $classMounts
3634
*/
3735
public function __construct(
3836
private ComponentTemplateFinderInterface $componentTemplateFinder,
@@ -92,7 +90,7 @@ public function mountFromObject(object $component, array $data, ComponentMetadat
9290
$originalData = $data;
9391
$data = $this->preMount($component, $data, $componentMetadata);
9492

95-
$this->mount($component, $data);
93+
$this->mount($component, $data, $componentMetadata);
9694

9795
// set data that wasn't set in mount on the component directly
9896
foreach ($data as $property => $value) {
@@ -144,30 +142,22 @@ public function get(string $name): object
144142
return $this->components->get($metadata->getName());
145143
}
146144

147-
private function mount(object $component, array &$data): void
145+
private function mount(object $component, array &$data, ComponentMetadata $componentMetadata): void
148146
{
149147
if ($component instanceof AnonymousComponent) {
150148
$component->mount($data);
151149

152150
return;
153151
}
154152

155-
if (null === (self::$mountMethods[$component::class] ?? null)) {
156-
try {
157-
$mountMethod = self::$mountMethods[$component::class] = (new \ReflectionClass($component))->getMethod('mount');
158-
} catch (\ReflectionException) {
159-
self::$mountMethods[$component::class] = false;
160-
161-
return;
162-
}
163-
}
164-
165-
if (false === $mountMethod ??= self::$mountMethods[$component::class]) {
153+
if (!$componentMetadata->getMounts()) {
166154
return;
167155
}
168156

157+
$mount = self::$mountMethods[$component::class] ??= (new \ReflectionClass($component))->getMethod('mount');
158+
169159
$parameters = [];
170-
foreach ($mountMethod->getParameters() as $refParameter) {
160+
foreach ($mount->getParameters() as $refParameter) {
171161
if (\array_key_exists($name = $refParameter->getName(), $data)) {
172162
$parameters[] = $data[$name];
173163
// remove the data element so it isn't used to set the property directly.
@@ -179,7 +169,7 @@ private function mount(object $component, array &$data): void
179169
}
180170
}
181171

182-
$mountMethod->invoke($component, ...$parameters);
172+
$mount->invoke($component, ...$parameters);
183173
}
184174

185175
private function preMount(object $component, array $data, ComponentMetadata $componentMetadata): array
@@ -188,9 +178,8 @@ private function preMount(object $component, array $data, ComponentMetadata $com
188178
$this->eventDispatcher->dispatch($event);
189179
$data = $event->getData();
190180

191-
$methods = self::$preMountMethods[$component::class] ??= AsTwigComponent::preMountMethods($component::class);
192-
foreach ($methods as $method) {
193-
if (null !== $newData = $method->invoke($component, $data)) {
181+
foreach ($componentMetadata->getPreMounts() as $preMount) {
182+
if (null !== $newData = $component->$preMount($data)) {
194183
$data = $newData;
195184
}
196185
}
@@ -207,9 +196,8 @@ private function postMount(object $component, array $data, ComponentMetadata $co
207196
$this->eventDispatcher->dispatch($event);
208197
$data = $event->getData();
209198

210-
$methods = self::$postMountMethods[$component::class] ??= AsTwigComponent::postMountMethods($component::class);
211-
foreach ($methods as $method) {
212-
if (null !== $newData = $method->invoke($component, $data)) {
199+
foreach ($componentMetadata->getPostMounts() as $postMount) {
200+
if (null !== $newData = $component->$postMount($data)) {
213201
$data = $newData;
214202
}
215203
}
@@ -257,7 +245,5 @@ private function throwUnknownComponentException(string $name): void
257245
public function reset(): void
258246
{
259247
self::$mountMethods = [];
260-
self::$preMountMethods = [];
261-
self::$postMountMethods = [];
262248
}
263249
}

src/TwigComponent/src/ComponentMetadata.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,36 @@ public function getAttributesVar(): string
6767
return $this->get('attributes_var', 'attributes');
6868
}
6969

70+
/**
71+
* @return list<string>
72+
*
73+
* @internal
74+
*/
75+
public function getPreMounts(): array
76+
{
77+
return $this->get('pre_mount', []);
78+
}
79+
80+
/**
81+
* @return list<string>
82+
*
83+
* @internal
84+
*/
85+
public function getMounts(): array
86+
{
87+
return $this->get('mount', []);
88+
}
89+
90+
/**
91+
* @return list<string>
92+
*
93+
* @internal
94+
*/
95+
public function getPostMounts(): array
96+
{
97+
return $this->get('post_mount', []);
98+
}
99+
70100
public function get(string $key, mixed $default = null): mixed
71101
{
72102
return $this->config[$key] ?? $default;

src/TwigComponent/src/DependencyInjection/Compiler/TwigComponentPass.php

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
use Symfony\Component\DependencyInjection\ContainerBuilder;
1717
use Symfony\Component\DependencyInjection\Exception\LogicException;
1818
use Symfony\Component\DependencyInjection\Reference;
19+
use Symfony\UX\TwigComponent\Attribute\PostMount;
20+
use Symfony\UX\TwigComponent\Attribute\PreMount;
1921

2022
/**
2123
* @author Kevin Bond <kevinbond@gmail.com>
@@ -68,7 +70,7 @@ public function process(ContainerBuilder $container): void
6870
$tag['service_id'] = $id;
6971
$tag['class'] = $definition->getClass();
7072
$tag['template'] = $tag['template'] ?? $this->calculateTemplate($tag['key'], $defaults);
71-
$componentConfig[$tag['key']] = $tag;
73+
$componentConfig[$tag['key']] = [...$tag, ...$this->getMountMethods($tag['class'])];
7274
$componentReferences[$tag['key']] = new Reference($id);
7375
$componentNames[] = $tag['key'];
7476
$componentClassMap[$tag['class']] = $tag['key'];
@@ -109,4 +111,34 @@ private function calculateTemplate(string $componentName, ?array $defaults): str
109111

110112
return \sprintf('%s/%s.html.twig', rtrim($directory, '/'), str_replace(':', '/', $componentName));
111113
}
114+
115+
/**
116+
* @param class-string $component
117+
* @return array{preMount: string[], mount: string[], postMount: string[]}
118+
*/
119+
private function getMountMethods(string $component): array
120+
{
121+
$preMount = $mount = $postMount = [];
122+
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
123+
foreach ($method->getAttributes(PreMount::class) as $attribute) {
124+
$preMount[$method->getName()] = $attribute->newInstance()->priority;
125+
}
126+
foreach ($method->getAttributes(PostMount::class) as $attribute) {
127+
$postMount[$method->getName()] = $attribute->newInstance()->priority;
128+
}
129+
if ('mount' === $method->getName()) {
130+
$mount['mount'] = 0;
131+
}
132+
}
133+
134+
arsort($preMount, SORT_NUMERIC);
135+
arsort($mount, SORT_NUMERIC);
136+
arsort($postMount, SORT_NUMERIC);
137+
138+
return [
139+
'pre_mount' => array_keys($preMount),
140+
'mount' => array_keys($mount),
141+
'post_mount' => array_keys($postMount),
142+
];
143+
}
112144
}

src/TwigComponent/tests/Fixtures/AcmeComponent/AcmeRootComponent.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@
77
#[AsTwigComponent]
88
class AcmeRootComponent
99
{
10-
1110
}

src/TwigComponent/tests/Fixtures/AcmeComponent/AcmeSubDir/AcmeOtherComponent.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@
77
#[AsTwigComponent]
88
class AcmeOtherComponent
99
{
10-
1110
}

src/TwigComponent/tests/Fixtures/Component/Conflict.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@
88
class Conflict
99
{
1010
public string $name;
11-
}
11+
}

src/TwigComponent/tests/Fixtures/Component/SubDirectory/ComponentInSubDirectory.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@
77
#[AsTwigComponent]
88
class ComponentInSubDirectory
99
{
10-
1110
}

src/TwigComponent/tests/Integration/ComponentExtensionTest.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,8 @@ public function testRenderingComponentWithNestedAttributes(): void
283283
{
284284
$output = $this->renderComponent('NestedAttributes');
285285

286-
$this->assertSame(<<<HTML
286+
$this->assertSame(
287+
<<<HTML
287288
<main>
288289
<div>
289290
<span>
@@ -302,7 +303,8 @@ public function testRenderingComponentWithNestedAttributes(): void
302303
'title:span:class' => 'baz',
303304
]);
304305

305-
$this->assertSame(<<<HTML
306+
$this->assertSame(
307+
<<<HTML
306308
<main class="foo">
307309
<div class="bar">
308310
<span class="baz">
@@ -324,7 +326,8 @@ public function testRenderingHtmlSyntaxComponentWithNestedAttributes(): void
324326
->render()
325327
;
326328

327-
$this->assertSame(<<<HTML
329+
$this->assertSame(
330+
<<<HTML
328331
<main>
329332
<div>
330333
<span>
@@ -343,7 +346,8 @@ public function testRenderingHtmlSyntaxComponentWithNestedAttributes(): void
343346
->render()
344347
;
345348

346-
$this->assertSame(<<<HTML
349+
$this->assertSame(
350+
<<<HTML
347351
<main class="foo" @class="vex">
348352
<div class="bar">
349353
<span class="baz">

src/TwigComponent/tests/Integration/ComponentFactoryTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public function testExceptionThrownIfRequiredMountParameterIsMissingFromPassedDa
9494

9595
public function testStringableObjectCanBePassedToComponent(): void
9696
{
97-
$attributes = $this->factory()->create('component_a', ['propB' => 'B', 'data-item-id-param' => new class {
97+
$attributes = $this->factory()->create('component_a', ['propB' => 'B', 'data-item-id-param' => new class () {
9898
public function __toString(): string
9999
{
100100
return 'test';

0 commit comments

Comments
 (0)