44
55namespace AutoMapper \Generator ;
66
7- use AutoMapper \Exception \CompileException ;
87use AutoMapper \Exception \MissingConstructorArgumentsException ;
98use AutoMapper \Generator \Shared \CachedReflectionStatementsGenerator ;
109use AutoMapper \Generator \Shared \DiscriminatorStatementsGenerator ;
1413use AutoMapper \Transformer \AllowNullValueTransformerInterface ;
1514use PhpParser \Node \Arg ;
1615use PhpParser \Node \Expr ;
17- use PhpParser \Node \Identifier ;
1816use PhpParser \Node \Name ;
1917use PhpParser \Node \Scalar ;
2018use PhpParser \Node \Stmt ;
21- use PhpParser \Parser ;
22- use PhpParser \ParserFactory ;
2319
2420use function AutoMapper \PhpParser \create_expr_array_item ;
2521use function AutoMapper \PhpParser \create_scalar_int ;
2925 */
3026final readonly class CreateTargetStatementsGenerator
3127{
32- private Parser $ parser ;
33-
3428 public function __construct (
3529 private DiscriminatorStatementsGenerator $ discriminatorStatementsGeneratorSource ,
3630 private DiscriminatorStatementsGenerator $ discriminatorStatementsGeneratorTarget ,
3731 private CachedReflectionStatementsGenerator $ cachedReflectionStatementsGenerator ,
3832 private PropertyConditionsGenerator $ propertyConditionsGenerator ,
39- ?Parser $ parser = null ,
4033 ) {
41- $ this ->parser = $ parser ?? (new ParserFactory ())->createForHostVersion ();
4234 }
4335
4436 /**
@@ -120,29 +112,35 @@ private function constructorArguments(GeneratorMetadata $metadata): array
120112 return [];
121113 }
122114
123- $ constructArguments = [];
124115 $ createObjectStatements = [];
116+ $ constructVar = $ metadata ->variableRegistry ->getVariableWithUniqueName ('constructArgs ' );
125117
126118 foreach ($ targetConstructor ->getParameters () as $ constructorParameter ) {
127119 // Find property for parameter
128120 $ propertyMetadata = $ metadata ->getTargetPropertyWithConstructor ($ constructorParameter ->getName ());
129121
130122 $ propertyStatements = null ;
131- $ constructArgument = null ;
132- $ constructorName = null ;
123+ $ assignVar = new Expr \ArrayDimFetch (
124+ $ constructVar ,
125+ new Scalar \String_ ($ constructorParameter ->getName ())
126+ );
133127
134128 if (null !== $ propertyMetadata ) {
135- [ $ propertyStatements, $ constructArgument , $ constructorName ] = $ this ->constructorArgument ($ metadata , $ propertyMetadata , $ constructorParameter );
129+ $ propertyStatements = $ this ->constructorArgument ($ assignVar , $ metadata , $ propertyMetadata , $ constructorParameter );
136130 }
137131
138- if (null === $ propertyStatements || null === $ constructArgument || null === $ constructorName ) {
139- [ $ propertyStatements, $ constructArgument , $ constructorName ] = $ this ->constructorArgumentWithoutSource ($ metadata , $ constructorParameter );
132+ if (null === $ propertyStatements ) {
133+ $ propertyStatements = $ this ->constructorArgumentWithoutSource ($ assignVar , $ metadata , $ constructorParameter );
140134 }
141135
142136 $ createObjectStatements = [...$ createObjectStatements , ...$ propertyStatements ];
143- $ constructArguments [$ constructorName ] = $ constructArgument ;
144137 }
145138
139+ $ createObjectStatements = [
140+ new Stmt \Expression (new Expr \Assign ($ constructVar , new Expr \Array_ ())),
141+ ...$ createObjectStatements ,
142+ ];
143+
146144 /*
147145 * Create object with named constructor arguments
148146 *
@@ -151,7 +149,9 @@ private function constructorArguments(GeneratorMetadata $metadata): array
151149 $ createObjectStatements [] = new Stmt \Expression (
152150 new Expr \Assign (
153151 $ metadata ->variableRegistry ->getResult (),
154- new Expr \New_ (new Name \FullyQualified ($ metadata ->mapperMetadata ->target ), $ constructArguments )
152+ new Expr \New_ (new Name \FullyQualified ($ metadata ->mapperMetadata ->target ), [
153+ new Arg ($ constructVar , unpack: true ),
154+ ])
155155 )
156156 );
157157
@@ -162,94 +162,101 @@ private function constructorArguments(GeneratorMetadata $metadata): array
162162 * If source missing a constructor argument, check if there is a constructor argument in the context, otherwise we use the default value or throw exception.
163163 *
164164 * ```php
165- *
166- * if ($value not defined) {
167- * $constructarg = MapperContext::hasConstructorArgument($context, $target, 'propertyName') ? MapperContext::getConstructorArgument($context, $target, 'propertyName') : {defaultValueExpr} // default value or throw exception
165+ * if ($value is defined) {
166+ * $constructarg['param'] = transformation of value
167+ * } elseif (MapperContext::hasConstructorArgument($context, $target, 'propertyName')) {
168+ * $constructarg['param'] = MapperContext::getConstructorArgument($context, $target, 'propertyName');
168169 * } else {
169- * $constructarg = transformation of value
170+ * // throw exception if no default expression and no null allowed
170171 * }
171172 * ```
172173 *
173- * @return array{ Stmt[], Arg, string}|array{ null, null, null}
174+ * @return list< Stmt>| null
174175 */
175- private function constructorArgument (GeneratorMetadata $ metadata , PropertyMetadata $ propertyMetadata , \ReflectionParameter $ parameter ): array
176+ private function constructorArgument (Expr \ ArrayDimFetch $ assignVar , GeneratorMetadata $ metadata , PropertyMetadata $ propertyMetadata , \ReflectionParameter $ parameter ): ? array
176177 {
177178 $ variableRegistry = $ metadata ->variableRegistry ;
178- $ constructVar = $ variableRegistry ->getVariableWithUniqueName ('constructArg ' );
179179 $ fieldValueExpr = $ propertyMetadata ->source ->accessor ?->getExpression($ variableRegistry ->getSourceInput ());
180180
181- $ condition = $ this ->propertyConditionsGenerator ->generate (
181+ $ conditionDefined = $ this ->propertyConditionsGenerator ->generate (
182182 $ metadata ,
183183 $ propertyMetadata ,
184184 true
185185 );
186186
187187 if (null === $ fieldValueExpr ) {
188188 if (!($ propertyMetadata ->transformer instanceof AllowNullValueTransformerInterface)) {
189- return [ null , null , null ] ;
189+ return null ;
190190 }
191191
192192 $ fieldValueExpr = new Expr \ConstFetch (new Name ('null ' ));
193193 }
194194
195- $ defaultValueExpr = new Expr \ Throw_ (
196- new Expr \ New_ ( new Name \ FullyQualified (MissingConstructorArgumentsException::class), [
197- new Arg ( new Scalar \ String_ ( sprintf ( ' Cannot create an instance of "%s" from mapping data because its constructor requires the following parameters to be present : "$%s". ' , $ metadata -> mapperMetadata -> target , $ propertyMetadata -> target -> property ))),
198- new Arg ( create_scalar_int ( 0 )),
199- new Arg ( new Expr \ConstFetch (new Name ('null ' ))),
200- new Arg ( new Expr \ Array_ ([
201- create_expr_array_item (new Scalar \ String_ ( $ propertyMetadata -> target -> property )),
202- ] )),
203- new Arg (new Scalar \ String_ ( $ metadata -> mapperMetadata -> target )),
204- ])
205- );
206-
207- if ( $ parameter -> isDefaultValueAvailable ()) {
208- $ defaultValueExpr = $ this -> getValueAsExpr ( $ parameter -> getDefaultValue ());
209- } elseif ( $ parameter -> allowsNull ()) {
210- $ defaultValueExpr = new Expr \ ConstFetch ( new Name ( ' null ' ));
195+ $ defaultValueExpr = null ;
196+
197+ if (! $ parameter -> isDefaultValueAvailable ()) {
198+ if ( $ parameter -> allowsNull ()) {
199+ $ defaultValueExpr = new Expr \ConstFetch (new Name ('null ' ));
200+ } else {
201+ $ defaultValueExpr = new Expr \ Throw_ ( new Expr \ New_ (new Name \ FullyQualified (MissingConstructorArgumentsException::class), [
202+ new Arg ( new Scalar \ String_ ( sprintf ( ' Cannot create an instance of "%s" from mapping data because its constructor requires the following parameters to be present : "$%s". ' , $ metadata -> mapperMetadata -> target , $ propertyMetadata -> target -> property ) )),
203+ new Arg (create_scalar_int ( 0 )),
204+ new Arg ( new Expr \ ConstFetch ( new Name ( ' null ' ))),
205+ new Arg ( new Expr \ Array_ ([
206+ create_expr_array_item ( new Scalar \ String_ ( $ propertyMetadata -> target -> property )),
207+ ])),
208+ new Arg ( new Scalar \ String_ ( $ metadata -> mapperMetadata -> target )),
209+ ]));
210+ }
211211 }
212212
213213 /* Get extract and transform statements for this property */
214- [$ output , $ propStatements ] = $ propertyMetadata ->transformer ->transform ($ fieldValueExpr , $ constructVar , $ propertyMetadata , $ variableRegistry ->getUniqueVariableScope (), $ variableRegistry ->getSourceInput ());
214+ [$ output , $ propStatements ] = $ propertyMetadata ->transformer ->transform ($ fieldValueExpr , $ assignVar , $ propertyMetadata , $ variableRegistry ->getUniqueVariableScope (), $ variableRegistry ->getSourceInput ());
215+
216+ $ hasConstructorArgument = new Expr \StaticCall (new Name \FullyQualified (MapperContext::class), 'hasConstructorArgument ' , [
217+ new Arg ($ variableRegistry ->getContext ()),
218+ new Arg (new Scalar \String_ ($ metadata ->mapperMetadata ->target )),
219+ new Arg (new Scalar \String_ ($ propertyMetadata ->target ->property )),
220+ ]);
221+ $ hasConstructorArgumentStmts = [
222+ new Stmt \Expression (new Expr \Assign ($ assignVar , new Expr \StaticCall (new Name \FullyQualified (MapperContext::class), 'getConstructorArgument ' , [
223+ new Arg ($ variableRegistry ->getContext ()),
224+ new Arg (new Scalar \String_ ($ metadata ->mapperMetadata ->target )),
225+ new Arg (new Scalar \String_ ($ propertyMetadata ->target ->property )),
226+ ]))),
227+ ];
215228
216- if (!$ condition ) {
229+ if (!$ conditionDefined ) {
217230 return [
218- [
219- ...$ propStatements ,
220- new Stmt \Expression (new Expr \Assign ($ constructVar , $ output )),
221- ],
222- new Arg ($ constructVar , name: new Identifier ($ parameter ->getName ())),
223- $ parameter ->getName (),
231+ ...$ propStatements ,
232+ new Stmt \Expression (new Expr \Assign ($ assignVar , $ output )),
224233 ];
225234 }
226235
227- return [
236+ $ if = new Stmt \If_ (
237+ $ conditionDefined ,
228238 [
229- new Stmt \If_ ($ condition , [
230- 'stmts ' => [
231- ...$ propStatements ,
232- new Stmt \Expression (new Expr \Assign ($ constructVar , $ output )),
233- ],
234- 'else ' => new Stmt \Else_ ([
235- new Stmt \Expression (new Expr \Assign ($ constructVar , new Expr \Ternary (
236- new Expr \StaticCall (new Name \FullyQualified (MapperContext::class), 'hasConstructorArgument ' , [
237- new Arg ($ variableRegistry ->getContext ()),
238- new Arg (new Scalar \String_ ($ metadata ->mapperMetadata ->target )),
239- new Arg (new Scalar \String_ ($ propertyMetadata ->target ->property )),
240- ]),
241- new Expr \StaticCall (new Name \FullyQualified (MapperContext::class), 'getConstructorArgument ' , [
242- new Arg ($ variableRegistry ->getContext ()),
243- new Arg (new Scalar \String_ ($ metadata ->mapperMetadata ->target )),
244- new Arg (new Scalar \String_ ($ propertyMetadata ->target ->property )),
245- ]),
246- $ defaultValueExpr ,
247- ))),
248- ]),
249- ]),
239+ 'stmts ' => [
240+ ...$ propStatements ,
241+ new Stmt \Expression (new Expr \Assign ($ assignVar , $ output )),
242+ ],
243+ 'elseifs ' => [
244+ new Stmt \ElseIf_ (
245+ $ hasConstructorArgument ,
246+ $ hasConstructorArgumentStmts ,
247+ ),
248+ ],
250249 ],
251- new Arg ($ constructVar , name: new Identifier ($ parameter ->getName ())),
252- $ parameter ->getName (),
250+ );
251+
252+ if ($ defaultValueExpr ) {
253+ $ if ->else = new Stmt \Else_ ([
254+ new Stmt \Expression (new Expr \Assign ($ assignVar , $ defaultValueExpr )),
255+ ]);
256+ }
257+
258+ return [
259+ $ if ,
253260 ];
254261 }
255262
@@ -261,50 +268,67 @@ private function constructorArgument(GeneratorMetadata $metadata, PropertyMetada
261268 * ? MapperContext::getConstructorArgument($context, $target, 'propertyName')
262269 * : {defaultValueExpr} // default value or throw exception
263270 * ```
271+ * ```php
272+ * if (MapperContext::hasConstructorArgument($context, $target, 'propertyName')) {}
273+ * $constructArgs['paramName'] = MapperContext::getConstructorArgument($context, $target, 'propertyName');
274+ * } else {
275+ * // throw exception if no default expression and no null allowed
276+ * throw new MissingConstructorArgumentsException('Cannot create an instance of "AutoMapper\Tests\Fixtures\ConstructorWithDefaultValuesAsObjects" from mapping data because its constructor requires the following parameters to be present : "$baz".', 0, null, ['baz'], 'AutoMapper\Tests\Fixtures\ConstructorWithDefaultValuesAsObjects');
277+ * // set null if no default expression and null allowed
278+ * $constructArgs['paramName'] = null;
279+ * }
280+ * ```
264281 *
265- * @return array{ Stmt[], Arg, string}
282+ * @return list< Stmt>
266283 */
267- private function constructorArgumentWithoutSource (GeneratorMetadata $ metadata , \ReflectionParameter $ constructorParameter ): array
284+ private function constructorArgumentWithoutSource (Expr \ ArrayDimFetch $ assignVar , GeneratorMetadata $ metadata , \ReflectionParameter $ constructorParameter ): array
268285 {
269286 $ variableRegistry = $ metadata ->variableRegistry ;
270- $ constructVar = $ variableRegistry -> getVariableWithUniqueName ( ' constructArg ' ) ;
271-
272- $ defaultValueExpr = new Expr \ Throw_ ( new Expr \ New_ ( new Name \ FullyQualified (MissingConstructorArgumentsException::class), [
273- new Arg ( new Scalar \ String_ ( sprintf ( ' Cannot create an instance of "%s" from mapping data because its constructor requires the following parameters to be present : "$%s". ' , $ metadata -> mapperMetadata -> target , $ constructorParameter ->getName ()))),
274- new Arg ( create_scalar_int ( 0 )),
275- new Arg ( new Expr \ ConstFetch ( new Name ( ' null ' ))),
276- new Arg (new Expr \Array_ ( [
277- create_expr_array_item (new Scalar \String_ ($ constructorParameter ->getName ())),
278- ] )),
279- new Arg (new Scalar \ String_ ( $ constructorParameter -> getName ( ))),
280- ]));
281-
282- if ( $ constructorParameter -> isDefaultValueAvailable ()) {
283- $ defaultValueExpr = $ this -> getValueAsExpr ( $ constructorParameter ->getDefaultValue ());
284- } elseif ( $ constructorParameter -> allowsNull ()) {
285- $ defaultValueExpr = new Expr \ ConstFetch ( new Name ( ' null ' ));
287+ $ defaultValueExpr = null ;
288+
289+ if (! $ constructorParameter -> isDefaultValueAvailable ()) {
290+ if ( $ constructorParameter ->allowsNull ()) {
291+ $ defaultValueExpr = new Expr \ ConstFetch ( new Name ( ' null ' ));
292+ } else {
293+ $ defaultValueExpr = new Expr \ Throw_ (new Expr \New_ ( new Name \ FullyQualified (MissingConstructorArgumentsException::class), [
294+ new Arg (new Scalar \String_ (sprintf ( ' Cannot create an instance of "%s" from mapping data because its constructor requires the following parameters to be present : "$%s". ' , $ metadata -> mapperMetadata -> target , $ constructorParameter ->getName () ))),
295+ new Arg ( create_scalar_int ( 0 )),
296+ new Arg (new Expr \ ConstFetch ( new Name ( ' null ' ))),
297+ new Arg ( new Expr \ Array_ ([
298+ create_expr_array_item ( new Scalar \ String_ ( $ constructorParameter -> getName ())),
299+ ])),
300+ new Arg ( new Scalar \ String_ ( $ constructorParameter ->getName ())),
301+ ]));
302+ }
286303 }
287304
288- return [
305+ $ if = new Stmt \If_ (
306+ new Expr \StaticCall (new Name \FullyQualified (MapperContext::class), 'hasConstructorArgument ' , [
307+ new Arg ($ variableRegistry ->getContext ()),
308+ new Arg (new Scalar \String_ ($ metadata ->mapperMetadata ->target )),
309+ new Arg (new Scalar \String_ ($ constructorParameter ->getName ())),
310+ ]),
289311 [
290- new Stmt \Expression (new Expr \Assign ($ constructVar ,
291- new Expr \Ternary (
292- new Expr \StaticCall (new Name \FullyQualified (MapperContext::class), 'hasConstructorArgument ' , [
293- new Arg ($ variableRegistry ->getContext ()),
294- new Arg (new Scalar \String_ ($ metadata ->mapperMetadata ->target )),
295- new Arg (new Scalar \String_ ($ constructorParameter ->getName ())),
296- ]),
312+ 'stmts ' => [
313+ new Stmt \Expression (new Expr \Assign ($ assignVar ,
297314 new Expr \StaticCall (new Name \FullyQualified (MapperContext::class), 'getConstructorArgument ' , [
298315 new Arg ($ variableRegistry ->getContext ()),
299316 new Arg (new Scalar \String_ ($ metadata ->mapperMetadata ->target )),
300317 new Arg (new Scalar \String_ ($ constructorParameter ->getName ())),
301- ]),
302- $ defaultValueExpr ,
303- ))
304- ),
305- ],
306- new Arg ($ constructVar , name: new Identifier ($ constructorParameter ->getName ())),
307- $ constructorParameter ->getName (),
318+ ])
319+ )),
320+ ],
321+ ]
322+ );
323+
324+ if ($ defaultValueExpr !== null ) {
325+ $ if ->else = new Stmt \Else_ ([
326+ new Stmt \Expression (new Expr \Assign ($ assignVar , $ defaultValueExpr )),
327+ ]);
328+ }
329+
330+ return [
331+ $ if ,
308332 ];
309333 }
310334
@@ -330,15 +354,4 @@ private function constructorWithoutArgument(GeneratorMetadata $metadata): ?Stmt
330354
331355 return new Stmt \Expression (new Expr \Assign ($ metadata ->variableRegistry ->getResult (), new Expr \New_ (new Name \FullyQualified ($ metadata ->mapperMetadata ->target ))));
332356 }
333-
334- private function getValueAsExpr (mixed $ value ): Expr
335- {
336- $ expr = $ this ->parser ->parse ('<?php ' . var_export ($ value , true ) . '; ' )[0 ] ?? null ;
337-
338- if ($ expr instanceof Stmt \Expression) {
339- return $ expr ->expr ;
340- }
341-
342- throw new CompileException ('Cannot extract expr from ' . var_export ($ value , true ));
343- }
344357}
0 commit comments