Files
Hugo Alliaume f025e483f4 bug #3372 [LiveComponent][TwigComponent] Fix reflection issues for private properties from trait and parent class (kachnitel)
This PR was squashed before being merged into the 2.x branch.

Discussion
----------

[LiveComponent][TwigComponent] Fix reflection issues for private properties from trait and parent class

| Q              | A
| -------------- | ---
| Bug fix?       | yes
| New feature?   | no
| Deprecations?  | no
| Documentation? | no
| Issues         | <!-- none yet -->
| License        | MIT

## Problem

When a component class *extends* another class that uses a trait with `#[ExposeInTemplate]` or `#[LiveProp]` attributes on **private** properties, those attributes are invisible to the metadata scanners.

PHP's `ReflectionClass::getProperties()` called on a child class does not return private properties from traits used in *ancestor* classes — they are only returned when `getProperties()` is called on the exact class that declares `use TraitName`. This is standard PHP reflection behaviour.

This affects any inheritance pattern where a base component uses `ComponentWithFormTrait` (or any custom trait with private `#[ExposeInTemplate]` properties) and app-level components extend it without re-declaring the trait.

**Symptom in `ux-twig-component`:** `Variable "form" does not exist` in the child component's Twig template.

**Symptom in `ux-live-component`:** `#[LiveProp(fieldName: 'callable()')]` declared in a trait used by a parent class is not registered with the correct `fieldName` on the child component, causing frontend model key mismatches.

## Fix

### `ux-twig-component` — `ComponentProperties::loadClassMetadata()`

Replace the single `$refClass->getProperties()` call with a loop that walks up `getParentClass()`, calling `getProperties()` at each level. Results are deduplicated by property name (child-class declaration takes priority), giving a complete view of all `#[ExposeInTemplate]` properties across the full hierarchy.

### `ux-live-component` — `LiveComponentMetadataFactory::createPropMetadatas()`

Pass `$property->getDeclaringClass()->getName()` instead of `$class->getName()` to `createLivePropMetadata()`. When a property is declared in a trait used by a parent class, the type extractor must be given the declaring class — not the leaf child class — to resolve the type correctly.

`#[LiveAction]` and `#[LiveListener]` on parent class methods are **not** affected: `getMethods()` already walks the full inheritance chain, so those are discovered correctly on child components.

## Tests

Added ``@group` trait-inheritance` tests in both packages using a minimal fixture: a trait with a private `#[ExposeInTemplate]` property / `#[LiveProp(fieldName: callable)]`, a parent component that uses the trait, and a child component that extends the parent without re-declaring the trait.

Commits
-------

799736ff76c [LiveComponent][TwigComponent] Fix reflection issues for private properties from trait and parent class
2026-03-15 19:48:53 +01:00
..