1+ <?php
2+
3+ namespace AutoMapper \EventListener \ObjectMapper ;
4+
5+ use AutoMapper \Event \GenerateMapperEvent ;
6+ use AutoMapper \Event \PropertyMetadataEvent ;
7+ use AutoMapper \Event \SourcePropertyMetadata ;
8+ use AutoMapper \Event \TargetPropertyMetadata ;
9+ use AutoMapper \Exception \BadMapDefinitionException ;
10+ use AutoMapper \Transformer \CallableTransformer ;
11+ use AutoMapper \Transformer \ExpressionLanguageTransformer ;
12+ use AutoMapper \Transformer \TransformerInterface ;
13+ use Symfony \Component \ExpressionLanguage \ExpressionLanguage ;
14+ use Symfony \Component \ExpressionLanguage \SyntaxError ;
15+ use Symfony \Component \ObjectMapper \Attribute \Map ;
16+
17+ final readonly class MapClassListener
18+ {
19+ public function __construct (
20+ private ExpressionLanguage $ expressionLanguage ,
21+ ) {
22+ }
23+
24+ public function __invoke (GenerateMapperEvent $ event ): void
25+ {
26+ // only handle class to class mapping
27+ if (!$ event ->mapperMetadata ->sourceReflectionClass || !$ event ->mapperMetadata ->targetReflectionClass ) {
28+ return ;
29+ }
30+
31+ $ mapAttribute = null ;
32+ $ reflectionClass = null ;
33+ $ isSource = false ;
34+
35+ foreach ($ event ->mapperMetadata ->sourceReflectionClass ->getAttributes (Map::class) as $ sourceAttribute ) {
36+ /** @var Map $attribute */
37+ $ attribute = $ sourceAttribute ->newInstance ();
38+
39+ if (!$ attribute ->target || $ attribute ->target === $ event ->mapperMetadata ->target ) {
40+ $ mapAttribute = $ attribute ;
41+ $ reflectionClass = $ event ->mapperMetadata ->sourceReflectionClass ;
42+ $ isSource = true ;
43+ break ;
44+ }
45+ }
46+
47+ if (!$ mapAttribute ) {
48+ foreach ($ event ->mapperMetadata ->targetReflectionClass ->getAttributes (Map::class) as $ targetAttribute ) {
49+ /** @var Map $attribute */
50+ $ attribute = $ targetAttribute ->newInstance ();
51+
52+ if (!$ attribute ->source || $ attribute ->source === $ event ->mapperMetadata ->source ) {
53+ $ mapAttribute = $ attribute ;
54+ $ reflectionClass = $ event ->mapperMetadata ->targetReflectionClass ;
55+ break ;
56+ }
57+ }
58+ }
59+
60+ if (!$ mapAttribute || !$ reflectionClass ) {
61+ return ;
62+ }
63+
64+ // get all properties
65+ $ properties = [];
66+
67+ foreach ($ reflectionClass ->getProperties () as $ property ) {
68+ foreach ($ property ->getAttributes (Map::class) as $ propertyAttribute ) {
69+ /** @var Map $attribute */
70+ $ attribute = $ propertyAttribute ->newInstance ();
71+ $ propertyMetadata = new PropertyMetadataEvent (
72+ /**
73+ * public ?string $if = null,// @TODO
74+ */
75+ $ event ->mapperMetadata ,
76+ new SourcePropertyMetadata ($ isSource ? $ property ->getName () : ($ attribute ->source ?? $ property ->getName ())),
77+ new TargetPropertyMetadata ($ isSource ? ($ attribute ->target ?? $ property ->getName ()) : $ property ->getName ()),
78+ transformer: $ this ->getTransformerFromMapAttribute ($ reflectionClass ->getName (), $ attribute , $ isSource ),
79+ if: $ attribute ->if ,
80+ );
81+
82+ $ properties [] = $ propertyMetadata ;
83+ }
84+ }
85+
86+ $ event ->properties = $ properties ;
87+
88+ if ($ mapAttribute ->transform ) {
89+ $ event ->provider = $ mapAttribute ->transform ;
90+ }
91+ }
92+
93+ protected function getTransformerFromMapAttribute (string $ class , Map $ attribute , bool $ fromSource = true ): ?TransformerInterface
94+ {
95+ $ transformer = null ;
96+
97+ if ($ attribute ->transform !== null ) {
98+ $ callableName = null ;
99+ $ transformerCallable = $ attribute ->transform ;
100+
101+ if ($ transformerCallable instanceof \Closure) {
102+ // This is not supported because we cannot generate code from a closure
103+ // However this should never be possible since attributes does not allow to pass a closure
104+ // Let's keep this check for future proof
105+ throw new BadMapDefinitionException ('Closure transformer is not supported. ' );
106+ }
107+
108+ if (\is_callable ($ transformerCallable , false , $ callableName )) {
109+ $ transformer = new CallableTransformer ($ callableName );
110+ } elseif (\is_string ($ transformerCallable ) && method_exists ($ class , $ transformerCallable )) {
111+ $ reflMethod = new \ReflectionMethod ($ class , $ transformerCallable );
112+
113+ if ($ reflMethod ->isStatic ()) {
114+ $ transformer = new CallableTransformer ($ class . ':: ' . $ transformerCallable );
115+ } else {
116+ $ transformer = new CallableTransformer ($ transformerCallable , $ fromSource , !$ fromSource );
117+ }
118+ } elseif (\is_string ($ transformerCallable )) {
119+ try {
120+ $ expression = $ this ->expressionLanguage ->compile ($ transformerCallable , ['value ' => 'source ' , 'context ' ]);
121+ } catch (SyntaxError $ e ) {
122+ throw new BadMapDefinitionException (\sprintf ('Transformer "%s" targeted by %s transformer on class "%s" is not valid. ' , $ transformerCallable , $ attribute ::class, $ class ), 0 , $ e );
123+ }
124+
125+ $ transformer = new ExpressionLanguageTransformer ($ expression );
126+ } else {
127+ throw new BadMapDefinitionException (\sprintf ('Callable "%s" targeted by %s transformer on class "%s" is not valid. ' , json_encode ($ transformerCallable ), $ attribute ::class, $ class ));
128+ }
129+ }
130+
131+ return $ transformer ;
132+ }
133+ }
0 commit comments