2
2
3
3
namespace ShipMonk \InputMapper \Compiler \Mapper \Object ;
4
4
5
+ use Nette \Utils \Arrays ;
5
6
use PhpParser \Node \Expr ;
7
+ use PhpParser \Node \Expr \MethodCall ;
8
+ use PhpParser \Node \VariadicPlaceholder ;
9
+ use PHPStan \PhpDocParser \Ast \Type \GenericTypeNode ;
6
10
use PHPStan \PhpDocParser \Ast \Type \IdentifierTypeNode ;
7
11
use PHPStan \PhpDocParser \Ast \Type \TypeNode ;
8
12
use ShipMonk \InputMapper \Compiler \CompiledExpr ;
9
13
use ShipMonk \InputMapper \Compiler \Mapper \MapperCompiler ;
10
14
use ShipMonk \InputMapper \Compiler \Php \PhpCodeBuilder ;
15
+ use ShipMonk \InputMapper \Runtime \CallbackMapper ;
16
+ use function count ;
11
17
12
18
class DelegateMapperCompiler implements MapperCompiler
13
19
{
14
20
15
21
/**
16
- * @param class-string $className
22
+ * @param list<MapperCompiler> $innerMapperCompilers
17
23
*/
18
24
public function __construct (
19
25
public readonly string $ className ,
26
+ public readonly array $ innerMapperCompilers = [],
20
27
)
21
28
{
22
29
}
23
30
24
31
public function compile (Expr $ value , Expr $ path , PhpCodeBuilder $ builder ): CompiledExpr
25
32
{
26
- $ shortName = $ builder -> importClass ( $ this -> className );
27
- $ provider = $ builder -> propertyFetch ( $ builder -> var ( ' this ' ), ' provider ' ) ;
28
- $ mapper = $ builder -> methodCall ( $ provider , ' get ' , [ $ builder -> classConstFetch ( $ shortName , ' class ' )]) ;
33
+ $ compilerMapper = $ this -> compileMapperExpr ( $ builder );
34
+ $ mapper = $ compilerMapper -> expr ;
35
+ $ statements = $ compilerMapper -> statements ;
29
36
$ mapped = $ builder ->methodCall ($ mapper , 'map ' , [$ value , $ path ]);
30
37
31
- return new CompiledExpr ($ mapped );
38
+ return new CompiledExpr ($ mapped, $ statements );
32
39
}
33
40
34
41
public function getInputType (): TypeNode
@@ -38,7 +45,76 @@ public function getInputType(): TypeNode
38
45
39
46
public function getOutputType (): TypeNode
40
47
{
41
- return new IdentifierTypeNode ($ this ->className );
48
+ $ outputType = new IdentifierTypeNode ($ this ->className );
49
+
50
+ if (count ($ this ->innerMapperCompilers ) === 0 ) {
51
+ return $ outputType ;
52
+ }
53
+
54
+ return new GenericTypeNode ($ outputType , Arrays::map (
55
+ $ this ->innerMapperCompilers ,
56
+ static function (MapperCompiler $ innerMapperCompiler ): TypeNode {
57
+ return $ innerMapperCompiler ->getOutputType ();
58
+ },
59
+ ));
60
+ }
61
+
62
+ /**
63
+ * @return list<Expr>
64
+ */
65
+ private function compileInnerMappers (PhpCodeBuilder $ builder ): array
66
+ {
67
+ $ innerMappers = [];
68
+
69
+ foreach ($ this ->innerMapperCompilers as $ key => $ innerMapperCompiler ) {
70
+ $ innerMappers [] = $ this ->compileInnerMapper ($ innerMapperCompiler , $ key , $ builder );
71
+ }
72
+
73
+ return $ innerMappers ;
74
+ }
75
+
76
+ private function compileInnerMapper (MapperCompiler $ innerMapperCompiler , int $ key , PhpCodeBuilder $ builder ): Expr
77
+ {
78
+ if ($ innerMapperCompiler instanceof self && count ($ innerMapperCompiler ->innerMapperCompilers ) === 0 ) {
79
+ $ provider = $ builder ->propertyFetch ($ builder ->var ('this ' ), 'provider ' );
80
+ $ innerClassExpr = $ builder ->classConstFetch ($ builder ->importClass ($ innerMapperCompiler ->className ), 'class ' );
81
+ return $ builder ->methodCall ($ provider , 'get ' , [$ innerClassExpr ]);
82
+ }
83
+
84
+ $ innerMapperMethodName = $ builder ->uniqMethodName ("mapInner {$ key }" );
85
+ $ innerMapperMethod = $ builder ->mapperMethod ($ innerMapperMethodName , $ innerMapperCompiler )->makePrivate ()->getNode ();
86
+ $ builder ->addMethod ($ innerMapperMethod );
87
+
88
+ $ innerMapperMethodCallback = new MethodCall ($ builder ->var ('this ' ), $ innerMapperMethodName , [new VariadicPlaceholder ()]);
89
+ return $ builder ->new ($ builder ->importClass (CallbackMapper::class), [$ innerMapperMethodCallback ]);
90
+ }
91
+
92
+ private function compileMapperExpr (PhpCodeBuilder $ builder ): CompiledExpr
93
+ {
94
+ foreach ($ builder ->getGenericParameters () as $ offset => $ genericParameter ) {
95
+ if ($ this ->className === $ genericParameter ->name ) {
96
+ $ innerMappers = $ builder ->propertyFetch ($ builder ->var ('this ' ), 'innerMappers ' );
97
+ $ innerMapper = $ builder ->arrayDimFetch ($ innerMappers , $ builder ->val ($ offset ));
98
+ return new CompiledExpr ($ innerMapper );
99
+ }
100
+ }
101
+
102
+ $ statements = [];
103
+ $ classNameExpr = $ builder ->classConstFetch ($ builder ->importClass ($ this ->className ), 'class ' );
104
+ $ provider = $ builder ->propertyFetch ($ builder ->var ('this ' ), 'provider ' );
105
+ $ innerMappers = $ this ->compileInnerMappers ($ builder );
106
+
107
+ if (count ($ innerMappers ) > 0 ) {
108
+ $ innerMappersVarName = $ builder ->uniqVariableName ('innerMappers ' );
109
+ $ statements [] = $ builder ->assign ($ builder ->var ($ innerMappersVarName ), $ builder ->val ($ innerMappers ));
110
+ $ getArguments = [$ classNameExpr , $ builder ->var ($ innerMappersVarName )];
111
+
112
+ } else {
113
+ $ getArguments = [$ classNameExpr ];
114
+ }
115
+
116
+ $ mapper = $ builder ->methodCall ($ provider , 'get ' , $ getArguments );
117
+ return new CompiledExpr ($ mapper , $ statements );
42
118
}
43
119
44
120
}
0 commit comments