1111
1212namespace Symfony \UX \TwigComponent ;
1313
14+ use Symfony \Component \PropertyAccess \PropertyAccessorInterface ;
1415use Symfony \Contracts \EventDispatcher \EventDispatcherInterface ;
16+ use Symfony \UX \TwigComponent \Attribute \ExposeInTemplate ;
1517use Symfony \UX \TwigComponent \EventListener \PreRenderEvent ;
1618use Twig \Environment ;
1719use Twig \Extension \EscaperExtension ;
@@ -25,7 +27,7 @@ final class ComponentRenderer
2527{
2628 private bool $ safeClassesRegistered = false ;
2729
28- public function __construct (private Environment $ twig , private EventDispatcherInterface $ dispatcher )
30+ public function __construct (private Environment $ twig , private EventDispatcherInterface $ dispatcher, private PropertyAccessorInterface $ propertyAccessor )
2931 {
3032 }
3133
@@ -37,14 +39,44 @@ public function render(object $component, ComponentMetadata $metadata): string
3739 $ this ->safeClassesRegistered = true ;
3840 }
3941
40- $ event = new PreRenderEvent (
41- $ component ,
42- $ metadata ,
43- array_merge (['this ' => $ component ], get_object_vars ($ component ))
42+ $ variables = array_merge (
43+ // add the component as "this"
44+ ['this ' => $ component ],
45+
46+ // expose all public properties
47+ get_object_vars ($ component ),
48+
49+ // expose properties/methods marked with ExposeInTemplate attribute
50+ iterator_to_array ($ this ->exposedVariables ($ component )),
4451 );
4552
46- $ this ->dispatcher ->dispatch ($ event );
53+ $ this ->dispatcher ->dispatch ($ event = new PreRenderEvent ( $ component , $ metadata , $ variables ) );
4754
4855 return $ this ->twig ->render ($ event ->getTemplate (), $ event ->getVariables ());
4956 }
57+
58+ private function exposedVariables (object $ component ): \Iterator
59+ {
60+ $ class = new \ReflectionClass ($ component );
61+
62+ foreach ($ class ->getProperties () as $ property ) {
63+ if (!$ attribute = $ property ->getAttributes (ExposeInTemplate::class)[0 ] ?? null ) {
64+ continue ;
65+ }
66+
67+ yield $ attribute ->newInstance ()->name ?? $ property ->name => $ this ->propertyAccessor ->getValue ($ component , $ property ->name );
68+ }
69+
70+ foreach ($ class ->getMethods (\ReflectionMethod::IS_PUBLIC ) as $ method ) {
71+ if (!$ attribute = $ method ->getAttributes (ExposeInTemplate::class)[0 ] ?? null ) {
72+ continue ;
73+ }
74+
75+ if (!($ name = $ attribute ->newInstance ()->name ) && str_starts_with ($ name = $ method ->name , 'get ' )) {
76+ $ name = lcfirst (substr ($ name , 3 ));
77+ }
78+
79+ yield $ name => $ component ->{$ method ->name }();
80+ }
81+ }
5082}
0 commit comments