Skip to content

Commit c67028f

Browse files
committed
[TwigComponent] Store mount methods in compiler pass
1 parent 7dff0df commit c67028f

File tree

13 files changed

+105
-166
lines changed

13 files changed

+105
-166
lines changed

src/LiveComponent/tests/Integration/LiveComponentHydratorTest.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1872,7 +1872,10 @@ public function getTest(LiveComponentMetadataFactory $metadataFactory): Hydratio
18721872
return new HydrationTestCase(
18731873
$this->component,
18741874
new LiveComponentMetadata(
1875-
new ComponentMetadata(['key' => '__testing']),
1875+
new ComponentMetadata([
1876+
'key' => '__testing',
1877+
'mount' => $reflectionClass->hasMethod('mount') ? ['mount'] : [],
1878+
]),
18761879
$metadataFactory->createPropMetadatas($reflectionClass),
18771880
),
18781881
$this->inputProps,

src/TwigComponent/src/Attribute/AsTwigComponent.php

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -75,46 +75,6 @@ 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

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: 14 additions & 28 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,42 +142,34 @@ 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.
174164
unset($data[$name]);
175165
} elseif ($refParameter->isDefaultValueAvailable()) {
176166
$parameters[] = $refParameter->getDefaultValue();
177167
} else {
178-
throw new \LogicException(\sprintf('%s::mount() has a required $%s parameter. Make sure to pass it or give it a default value.', $component::class, $name));
168+
throw new \LogicException(\sprintf('%s has a required $%s parameter. Make sure to pass it or give it a default value.', $component::class.'::mount()', $name));
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: 34 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 <[email protected]>
@@ -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,35 @@ 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+
*
118+
* @return array{preMount: string[], mount: string[], postMount: string[]}
119+
*/
120+
private function getMountMethods(string $component): array
121+
{
122+
$preMount = $mount = $postMount = [];
123+
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
124+
foreach ($method->getAttributes(PreMount::class) as $attribute) {
125+
$preMount[$method->getName()] = $attribute->newInstance()->priority;
126+
}
127+
foreach ($method->getAttributes(PostMount::class) as $attribute) {
128+
$postMount[$method->getName()] = $attribute->newInstance()->priority;
129+
}
130+
if ('mount' === $method->getName()) {
131+
$mount['mount'] = 0;
132+
}
133+
}
134+
135+
arsort($preMount, \SORT_NUMERIC);
136+
arsort($mount, \SORT_NUMERIC);
137+
arsort($postMount, \SORT_NUMERIC);
138+
139+
return [
140+
'pre_mount' => array_keys($preMount),
141+
'mount' => array_keys($mount),
142+
'post_mount' => array_keys($postMount),
143+
];
144+
}
112145
}

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
}

0 commit comments

Comments
 (0)