Skip to content

Commit

Permalink
feature #2357 [TwigComponent] Store mount methods in compiler pass (s…
Browse files Browse the repository at this point in the history
…mnandre)

This PR was squashed before being merged into the 2.x branch.

Discussion
----------

[TwigComponent] Store mount methods in compiler pass

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | no
| Issues        | Fix #...
| License       | MIT

(internal optimization)

* collection mount metadata (premount, mount, postmount) in  compiler pass
* store in ComponentMetadata object
* start removing methods from AsTwigComponent

Note: i had to update an internal test method in LiveComponent

Commits
-------

c67028f [TwigComponent] Store mount methods in compiler pass
  • Loading branch information
smnandre committed Nov 10, 2024
2 parents e74c44e + c67028f commit 222973e
Show file tree
Hide file tree
Showing 13 changed files with 105 additions and 166 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1872,7 +1872,10 @@ public function getTest(LiveComponentMetadataFactory $metadataFactory): Hydratio
return new HydrationTestCase(
$this->component,
new LiveComponentMetadata(
new ComponentMetadata(['key' => '__testing']),
new ComponentMetadata([
'key' => '__testing',
'mount' => $reflectionClass->hasMethod('mount') ? ['mount'] : [],
]),
$metadataFactory->createPropMetadatas($reflectionClass),
),
$this->inputProps,
Expand Down
40 changes: 0 additions & 40 deletions src/TwigComponent/src/Attribute/AsTwigComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,46 +75,6 @@ public function serviceConfig(): array
];
}

/**
* @param object|class-string $component
*
* @internal
*/
public static function mountMethod(object|string $component): ?\ReflectionMethod
{
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
if ('mount' === $method->getName()) {
return $method;
}
}

return null;
}

/**
* @param object|class-string $component
*
* @return \ReflectionMethod[]
*
* @internal
*/
public static function postMountMethods(object|string $component): array
{
return self::attributeMethodsByPriorityFor($component, PostMount::class);
}

/**
* @param object|class-string $component
*
* @return \ReflectionMethod[]
*
* @internal
*/
public static function preMountMethods(object|string $component): array
{
return self::attributeMethodsByPriorityFor($component, PreMount::class);
}

/**
* @param object|class-string $component
* @param class-string $attributeClass
Expand Down
18 changes: 9 additions & 9 deletions src/TwigComponent/src/Command/TwigComponentDebugCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Finder\Finder;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Attribute\ExposeInTemplate;
use Symfony\UX\TwigComponent\ComponentFactory;
use Symfony\UX\TwigComponent\ComponentMetadata;
Expand Down Expand Up @@ -214,7 +213,7 @@ private function displayComponentDetails(SymfonyStyle $io, string $name): void
]);

// Anonymous Component
if (null === $metadata->get('class')) {
if ($metadata->isAnonymous()) {
$table->addRows([
['Type', '<comment>Anonymous</comment>'],
new TableSeparator(),
Expand All @@ -229,7 +228,7 @@ private function displayComponentDetails(SymfonyStyle $io, string $name): void
['Type', $metadata->get('live') ? '<info>Live</info>' : ''],
new TableSeparator(),
// ['Attributes Var', $metadata->get('attributes_var')],
['Public Props', $metadata->get('expose_public_props') ? 'Yes' : 'No'],
['Public Props', $metadata->isPublicPropsExposed() ? 'Yes' : 'No'],
['Properties', implode("\n", $this->getComponentProperties($metadata))],
]);

Expand All @@ -242,14 +241,15 @@ private function displayComponentDetails(SymfonyStyle $io, string $name): void
return \sprintf('%s(%s)', $m->getName(), implode(', ', $params));
};
$hooks = [];
if ($method = AsTwigComponent::mountMethod($metadata->getClass())) {
$hooks[] = ['Mount', $logMethod($method)];
$reflector = new \ReflectionClass($metadata->getClass());
foreach ($metadata->getPreMounts() as $method) {
$hooks[] = ['PreMount', $logMethod($reflector->getMethod($method))];
}
foreach (AsTwigComponent::preMountMethods($metadata->getClass()) as $method) {
$hooks[] = ['PreMount', $logMethod($method)];
foreach ($metadata->getMounts() as $method) {
$hooks[] = ['Mount', $logMethod($reflector->getMethod($method))];
}
foreach (AsTwigComponent::postMountMethods($metadata->getClass()) as $method) {
$hooks[] = ['PostMount', $logMethod($method)];
foreach ($metadata->getPostMounts() as $method) {
$hooks[] = ['PostMount', $logMethod($reflector->getMethod($method))];
}
if ($hooks) {
$table->addRows([
Expand Down
42 changes: 14 additions & 28 deletions src/TwigComponent/src/ComponentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
use Symfony\Contracts\Service\ResetInterface;
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
use Symfony\UX\TwigComponent\Event\PostMountEvent;
use Symfony\UX\TwigComponent\Event\PreMountEvent;

Expand All @@ -26,13 +25,12 @@
*/
final class ComponentFactory implements ResetInterface
{
private static $mountMethods = [];
private static $preMountMethods = [];
private static $postMountMethods = [];
private static array $mountMethods = [];

/**
* @param array<string, array> $config
* @param array<class-string, string> $classMap
* @param array<class-string, array<string, string[]> $classMounts
*/
public function __construct(
private ComponentTemplateFinderInterface $componentTemplateFinder,
Expand Down Expand Up @@ -92,7 +90,7 @@ public function mountFromObject(object $component, array $data, ComponentMetadat
$originalData = $data;
$data = $this->preMount($component, $data, $componentMetadata);

$this->mount($component, $data);
$this->mount($component, $data, $componentMetadata);

// set data that wasn't set in mount on the component directly
foreach ($data as $property => $value) {
Expand Down Expand Up @@ -144,42 +142,34 @@ public function get(string $name): object
return $this->components->get($metadata->getName());
}

private function mount(object $component, array &$data): void
private function mount(object $component, array &$data, ComponentMetadata $componentMetadata): void
{
if ($component instanceof AnonymousComponent) {
$component->mount($data);

return;
}

if (null === (self::$mountMethods[$component::class] ?? null)) {
try {
$mountMethod = self::$mountMethods[$component::class] = (new \ReflectionClass($component))->getMethod('mount');
} catch (\ReflectionException) {
self::$mountMethods[$component::class] = false;

return;
}
}

if (false === $mountMethod ??= self::$mountMethods[$component::class]) {
if (!$componentMetadata->getMounts()) {
return;
}

$mount = self::$mountMethods[$component::class] ??= (new \ReflectionClass($component))->getMethod('mount');

$parameters = [];
foreach ($mountMethod->getParameters() as $refParameter) {
foreach ($mount->getParameters() as $refParameter) {
if (\array_key_exists($name = $refParameter->getName(), $data)) {
$parameters[] = $data[$name];
// remove the data element so it isn't used to set the property directly.
unset($data[$name]);
} elseif ($refParameter->isDefaultValueAvailable()) {
$parameters[] = $refParameter->getDefaultValue();
} else {
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));
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));
}
}

$mountMethod->invoke($component, ...$parameters);
$mount->invoke($component, ...$parameters);
}

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

$methods = self::$preMountMethods[$component::class] ??= AsTwigComponent::preMountMethods($component::class);
foreach ($methods as $method) {
if (null !== $newData = $method->invoke($component, $data)) {
foreach ($componentMetadata->getPreMounts() as $preMount) {
if (null !== $newData = $component->$preMount($data)) {
$data = $newData;
}
}
Expand All @@ -207,9 +196,8 @@ private function postMount(object $component, array $data, ComponentMetadata $co
$this->eventDispatcher->dispatch($event);
$data = $event->getData();

$methods = self::$postMountMethods[$component::class] ??= AsTwigComponent::postMountMethods($component::class);
foreach ($methods as $method) {
if (null !== $newData = $method->invoke($component, $data)) {
foreach ($componentMetadata->getPostMounts() as $postMount) {
if (null !== $newData = $component->$postMount($data)) {
$data = $newData;
}
}
Expand Down Expand Up @@ -257,7 +245,5 @@ private function throwUnknownComponentException(string $name): void
public function reset(): void
{
self::$mountMethods = [];
self::$preMountMethods = [];
self::$postMountMethods = [];
}
}
30 changes: 30 additions & 0 deletions src/TwigComponent/src/ComponentMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,36 @@ public function getAttributesVar(): string
return $this->get('attributes_var', 'attributes');
}

/**
* @return list<string>
*
* @internal
*/
public function getPreMounts(): array
{
return $this->get('pre_mount', []);
}

/**
* @return list<string>
*
* @internal
*/
public function getMounts(): array
{
return $this->get('mount', []);
}

/**
* @return list<string>
*
* @internal
*/
public function getPostMounts(): array
{
return $this->get('post_mount', []);
}

public function get(string $key, mixed $default = null): mixed
{
return $this->config[$key] ?? $default;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\UX\TwigComponent\Attribute\PostMount;
use Symfony\UX\TwigComponent\Attribute\PreMount;

/**
* @author Kevin Bond <[email protected]>
Expand Down Expand Up @@ -68,7 +70,7 @@ public function process(ContainerBuilder $container): void
$tag['service_id'] = $id;
$tag['class'] = $definition->getClass();
$tag['template'] = $tag['template'] ?? $this->calculateTemplate($tag['key'], $defaults);
$componentConfig[$tag['key']] = $tag;
$componentConfig[$tag['key']] = [...$tag, ...$this->getMountMethods($tag['class'])];
$componentReferences[$tag['key']] = new Reference($id);
$componentNames[] = $tag['key'];
$componentClassMap[$tag['class']] = $tag['key'];
Expand Down Expand Up @@ -109,4 +111,35 @@ private function calculateTemplate(string $componentName, ?array $defaults): str

return \sprintf('%s/%s.html.twig', rtrim($directory, '/'), str_replace(':', '/', $componentName));
}

/**
* @param class-string $component
*
* @return array{preMount: string[], mount: string[], postMount: string[]}
*/
private function getMountMethods(string $component): array
{
$preMount = $mount = $postMount = [];
foreach ((new \ReflectionClass($component))->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
foreach ($method->getAttributes(PreMount::class) as $attribute) {
$preMount[$method->getName()] = $attribute->newInstance()->priority;
}
foreach ($method->getAttributes(PostMount::class) as $attribute) {
$postMount[$method->getName()] = $attribute->newInstance()->priority;
}
if ('mount' === $method->getName()) {
$mount['mount'] = 0;
}
}

arsort($preMount, \SORT_NUMERIC);
arsort($mount, \SORT_NUMERIC);
arsort($postMount, \SORT_NUMERIC);

return [
'pre_mount' => array_keys($preMount),
'mount' => array_keys($mount),
'post_mount' => array_keys($postMount),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
#[AsTwigComponent]
class AcmeRootComponent
{

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
#[AsTwigComponent]
class AcmeOtherComponent
{

}
2 changes: 1 addition & 1 deletion src/TwigComponent/tests/Fixtures/Component/Conflict.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
class Conflict
{
public string $name;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
#[AsTwigComponent]
class ComponentInSubDirectory
{

}
12 changes: 8 additions & 4 deletions src/TwigComponent/tests/Integration/ComponentExtensionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ public function testRenderingComponentWithNestedAttributes(): void
{
$output = $this->renderComponent('NestedAttributes');

$this->assertSame(<<<HTML
$this->assertSame(
<<<HTML
<main>
<div>
<span>
Expand All @@ -302,7 +303,8 @@ public function testRenderingComponentWithNestedAttributes(): void
'title:span:class' => 'baz',
]);

$this->assertSame(<<<HTML
$this->assertSame(
<<<HTML
<main class="foo">
<div class="bar">
<span class="baz">
Expand All @@ -324,7 +326,8 @@ public function testRenderingHtmlSyntaxComponentWithNestedAttributes(): void
->render()
;

$this->assertSame(<<<HTML
$this->assertSame(
<<<HTML
<main>
<div>
<span>
Expand All @@ -343,7 +346,8 @@ public function testRenderingHtmlSyntaxComponentWithNestedAttributes(): void
->render()
;

$this->assertSame(<<<HTML
$this->assertSame(
<<<HTML
<main class="foo" @class="vex">
<div class="bar">
<span class="baz">
Expand Down
Loading

0 comments on commit 222973e

Please sign in to comment.