ClassDefinition::setName()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
5
 * Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
6
 */
7
8
namespace Spryker\Zed\Transfer\Business\Model\Generator;
9
10
use ArrayObject;
11
use Laminas\Filter\Word\CamelCaseToUnderscore;
12
use Laminas\Filter\Word\UnderscoreToCamelCase;
13
use Spryker\DecimalObject\Decimal;
14
use Spryker\Shared\Kernel\Transfer\AbstractAttributesTransfer;
15
use Spryker\Shared\Kernel\Transfer\AbstractTransfer;
16
use Spryker\Shared\Transfer\TypeValidation\TransferTypeValidatorTrait;
17
use Spryker\Zed\Transfer\Business\Exception\InvalidAbstractAttributesUsageException;
18
use Spryker\Zed\Transfer\Business\Exception\InvalidAssociativeTypeException;
19
use Spryker\Zed\Transfer\Business\Exception\InvalidAssociativeValueException;
20
use Spryker\Zed\Transfer\Business\Exception\InvalidNameException;
21
use Spryker\Zed\Transfer\Business\Exception\InvalidSingularPropertyNameException;
22
use Spryker\Zed\Transfer\TransferConfig;
23
24
class ClassDefinition implements ClassDefinitionInterface
25
{
26
    /**
27
     * @var string
28
     */
29
    public const TYPE_FULLY_QUALIFIED = 'type_fully_qualified';
30
31
    /**
32
     * @var string
33
     */
34
    public const DEFAULT_ASSOCIATIVE_ARRAY_TYPE = 'string|int';
35
36
    /**
37
     * @var string
38
     */
39
    protected const EXTRA_TYPE_HINTS = 'extra_type_hints';
40
41
    /**
42
     * @var array<string, array<string, string>>
43
     */
44
    protected const SUPPORTED_VALUE_OBJECTS = [
45
        'decimal' => [
46
            self::TYPE_FULLY_QUALIFIED => Decimal::class,
47
            self::EXTRA_TYPE_HINTS => 'string|int|float',
48
        ],
49
    ];
50
51
    /**
52
     * @var string
53
     */
54
    protected const SHIM_NOTICE_TEMPLATE = 'Forward compatibility warning: %s is the actual type (please use that, %s is kept for BC).';
55
56
    /**
57
     * @var string
58
     */
59
    protected $name;
60
61
    /**
62
     * @var array<string, array>
63
     */
64
    protected $constants = [];
65
66
    /**
67
     * @var array<string, array>
68
     */
69
    protected $properties = [];
70
71
    /**
72
     * @var array<array>
73
     */
74
    protected $normalizedProperties = [];
75
76
    /**
77
     * @var array<string, array>
78
     */
79
    protected $methods = [];
80
81
    /**
82
     * @var array
83
     */
84
    protected $constructorDefinition = [];
85
86
    /**
87
     * @var string|null
88
     */
89
    protected $deprecationDescription;
90
91
    /**
92
     * @var array<string, string>
93
     */
94
    protected $useStatements = [];
95
96
    /**
97
     * @var array<string, string>
98
     */
99
    protected $propertyNameMap = [];
100
101
    /**
102
     * @var string|null
103
     */
104
    protected $entityNamespace;
105
106
    /**
107
     * @var \Spryker\Zed\Transfer\TransferConfig
108
     */
109
    protected $transferConfig;
110
111
    /**
112
     * @param \Spryker\Zed\Transfer\TransferConfig $transferConfig
113
     */
114
    public function __construct(TransferConfig $transferConfig)
115
    {
116
        $this->transferConfig = $transferConfig;
117
    }
118
119
    /**
120
     * @param array<string, mixed> $definition
121
     *
122
     * @return $this
123
     */
124
    public function setDefinition(array $definition)
125
    {
126
        $this->setName($definition['name']);
127
128
        if (isset($definition['deprecated'])) {
129
            $this->deprecationDescription = $definition['deprecated'];
130
        }
131
132
        $this->addEntityNamespace($definition);
133
        $this->addExtraUseStatements();
134
135
        if (isset($definition['property'])) {
136
            $this->addAbstractAttributesTransferUseStatement($definition['property']);
137
            $definition = $this->shimTransferDefinitionPropertyTypes($definition);
138
            $properties = $this->normalizePropertyTypes($definition['property']);
139
            $this->addConstants($properties);
140
            $this->addProperties($properties);
141
            $this->setPropertyNameMap($properties);
142
            $this->addMethods($properties);
143
        }
144
145
        return $this;
146
    }
147
148
    /**
149
     * @param string $name
150
     *
151
     * @return $this
152
     */
153
    protected function setName($name)
154
    {
155
        if (!$this->transferConfig->isTransferNameValidated()) {
156
            $this->setNameWithoutValidation($name);
157
158
            return $this;
159
        }
160
161
        $this->assertValidName($name);
162
163
        $this->name = $name . 'Transfer';
164
165
        return $this;
166
    }
167
168
    /**
169
     * BC shim to use strict generation only as feature flag to be
170
     * enabled manually on project level.
171
     *
172
     * @deprecated Will be removed with the next major to enforce validation then.
173
     *
174
     * @param string $name
175
     *
176
     * @return $this
177
     */
178
    protected function setNameWithoutValidation(string $name)
179
    {
180
        if (strpos($name, 'Transfer') === false) {
181
            $name .= 'Transfer';
182
        }
183
184
        $this->name = ucfirst($name);
185
186
        return $this;
187
    }
188
189
    /**
190
     * @return string
191
     */
192
    public function getName(): string
193
    {
194
        return $this->name;
195
    }
196
197
    /**
198
     * @return array<string>
199
     */
200
    public function getUseStatements(): array
201
    {
202
        return $this->useStatements;
203
    }
204
205
    /**
206
     * @param array $properties
207
     *
208
     * @return void
209
     */
210
    protected function addConstants(array $properties): void
211
    {
212
        foreach ($properties as $property) {
213
            $this->addConstant($property);
214
        }
215
    }
216
217
    /**
218
     * @param array<string, mixed> $property
219
     *
220
     * @return void
221
     */
222
    protected function addConstant(array $property): void
223
    {
224
        $property['name'] = lcfirst($property['name']);
225
        $propertyInfo = [
226
            'name' => $this->getPropertyConstantName($property),
227
            'value' => $property['name'],
228
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
229
        ];
230
231
        $this->constants[$property['name']] = $propertyInfo;
232
    }
233
234
    /**
235
     * @param array $properties
236
     *
237
     * @return void
238
     */
239
    protected function addProperties(array $properties): void
240
    {
241
        foreach ($properties as $property) {
242
            $this->assertAbstractAttributesTransferType($property);
243
            $this->addProperty($property);
244
        }
245
    }
246
247
    /**
248
     * @param array<string, mixed> $property
249
     *
250
     * @return void
251
     */
252
    protected function addProperty(array $property): void
253
    {
254
        $property['name'] = lcfirst($property['name']);
255
        $propertyInfo = [
256
            'name' => $property['name'],
257
            'type' => $this->buildPropertyType($property),
258
            'bundles' => $property['bundles'],
259
            'is_associative' => $property['is_associative'],
260
            'is_array_collection' => $this->isArrayCollection($property),
261
        ];
262
263
        $this->properties[$property['name']] = $propertyInfo;
264
    }
265
266
    /**
267
     * @param array $properties
268
     *
269
     * @return void
270
     */
271
    protected function setPropertyNameMap(array $properties): void
272
    {
273
        /** @var array<string, string> $property */
274
        foreach ($properties as $property) {
275
            $nameCamelCase = $this->getPropertyName($property);
276
            $this->propertyNameMap[$property['name_underscore']] = $nameCamelCase;
277
            $this->propertyNameMap[$nameCamelCase] = $nameCamelCase;
278
            $this->propertyNameMap[ucfirst($nameCamelCase)] = $nameCamelCase;
279
        }
280
    }
281
282
    /**
283
     * Properties which are Transfer MUST be suffixed with Transfer
284
     *
285
     * @param array $properties
286
     *
287
     * @return array
288
     */
289
    protected function normalizePropertyTypes(array $properties): array
290
    {
291
        $normalizedProperties = [];
292
        foreach ($properties as $property) {
293
            $this->assertProperty($property);
294
295
            $property[static::TYPE_FULLY_QUALIFIED] = $property['type'];
296
            $property['is_collection'] = false;
297
            $property['is_primitive_array'] = false;
298
            $property['is_transfer'] = false;
299
            $property['is_value_object'] = false;
300
            $property['propertyConst'] = $this->getPropertyConstantName($property);
301
            $property['name_underscore'] = mb_strtolower($property['propertyConst']);
302
303
            if ($this->isTransferOrTransferArray($property['type'])) {
304
                $property = $this->buildTransferPropertyDefinition($property);
305
            }
306
307
            if ($this->isArray($property) && $this->transferConfig->isArrayRequireValidationEnabled()) {
308
                $property = $this->buildArrayPropertyDefinition($property);
309
            }
310
311
            if ($this->isValueObject($property)) {
312
                $property = $this->buildValueObjectPropertyDefinition($property);
313
            }
314
            if ($this->isAbstractAttributesTransfer($property['type'])) {
315
                $property = $this->buildAbstractAttributesTransferPropertyDefinition($property);
316
            }
317
318
            $property['is_associative'] = $this->isAssociativeArray($property);
319
            $property['is_strict'] = $this->isStrictProperty($property);
320
321
            $normalizedProperties[] = $property;
322
        }
323
324
        $this->normalizedProperties = $normalizedProperties;
325
326
        return $normalizedProperties;
327
    }
328
329
    /**
330
     * @param array<string, mixed> $property
331
     *
332
     * @return array
333
     */
334
    protected function buildTransferPropertyDefinition(array $property): array
335
    {
336
        $property['is_transfer'] = true;
337
        $property[static::TYPE_FULLY_QUALIFIED] = 'Generated\\Shared\\Transfer\\';
338
339
        if (preg_match('/\[\]$/', $property['type'])) {
340
            $property['type'] = str_replace('[]', '', $property['type']) . 'Transfer[]';
341
            $property[static::TYPE_FULLY_QUALIFIED] = 'Generated\\Shared\\Transfer\\' . str_replace('[]', '', $property['type']);
342
            $property['is_collection'] = true;
343
344
            return $property;
345
        }
346
        $property['type'] .= 'Transfer';
347
        $property[static::TYPE_FULLY_QUALIFIED] .= $property['type'];
348
349
        return $property;
350
    }
351
352
    /**
353
     * @param array<string, mixed> $property
354
     *
355
     * @return array
356
     */
357
    protected function buildArrayPropertyDefinition(array $property): array
358
    {
359
        $property['is_primitive_array'] = true;
360
361
        return $property;
362
    }
363
364
    /**
365
     * @param array<string, mixed> $property
366
     *
367
     * @return array
368
     */
369
    protected function buildAbstractAttributesTransferPropertyDefinition(array $property): array
370
    {
371
        $property['is_transfer'] = true;
372
        $property[static::TYPE_FULLY_QUALIFIED] = AbstractAttributesTransfer::class;
373
374
        return $property;
375
    }
376
377
    /**
378
     * @param array<string, mixed> $property
379
     *
380
     * @return array
381
     */
382
    protected function buildValueObjectPropertyDefinition(array $property): array
383
    {
384
        $property['is_value_object'] = true;
385
        $property[static::TYPE_FULLY_QUALIFIED] = $this->getValueObjectFullyQualifiedClassName($property);
386
387
        return $property;
388
    }
389
390
    /**
391
     * @param string $type
392
     *
393
     * @return bool
394
     */
395
    protected function isTransferOrTransferArray($type): bool
396
    {
397
        return (bool)preg_match('/^[A-Z].*/', $type);
398
    }
399
400
    /**
401
     * @param string $type
402
     *
403
     * @return bool
404
     */
405
    protected function isAbstractAttributesTransfer(string $type): bool
406
    {
407
        return $type === 'AbstractAttributesTransfer';
408
    }
409
410
    /**
411
     * @param array<string, mixed> $property
412
     *
413
     * @return bool
414
     */
415
    protected function isValueObject(array $property): bool
416
    {
417
        return isset(static::SUPPORTED_VALUE_OBJECTS[$property['type']]);
418
    }
419
420
    /**
421
     * @param array<string, mixed> $property
422
     *
423
     * @return string
424
     */
425
    protected function getPropertyType(array $property): string
426
    {
427
        if ($this->isValueObject($property)) {
428
            return sprintf('\%s|null', $this->getValueObjectFullyQualifiedClassName($property));
429
        }
430
431
        if ($this->isTypedArray($property)) {
432
            $type = preg_replace('/\[\]/', '', $property['type']);
433
434
            return $type . '[]';
435
        }
436
437
        if ($this->isPrimitiveArray($property)) {
438
            return 'array|null';
439
        }
440
441
        if ($this->isArrayCollection($property)) {
442
            return 'array';
443
        }
444
445
        if ($this->isTypeAbstractAttributesTransfer($property)) {
446
            return sprintf('\%s|\null', AbstractTransfer::class);
447
        }
448
449
        if ($this->isCollection($property)) {
450
            return $this->buildCollectionDocTypeHint($property);
451
        }
452
453
        if ($this->isTypeTransferObject($property)) {
454
            return '\Generated\Shared\Transfer\\' . $property['type'] . '|null';
455
        }
456
457
        return $property['type'] . '|null';
458
    }
459
460
    /**
461
     * @param array<string, mixed> $property
462
     *
463
     * @return bool
464
     */
465
    protected function isTypeTransferObject(array $property): bool
466
    {
467
        return ($property['is_transfer']);
468
    }
469
470
    /**
471
     * @param array<string, mixed> $property
472
     *
473
     * @return bool
474
     */
475
    protected function isTypeAbstractAttributesTransfer(array $property): bool
476
    {
477
        return ($property['is_transfer'] && $property[static::TYPE_FULLY_QUALIFIED] === AbstractAttributesTransfer::class);
478
    }
479
480
    /**
481
     * @param array<string, mixed> $property
482
     *
483
     * @return string
484
     */
485
    protected function getSetVar(array $property): string
486
    {
487
        if ($this->isValueObject($property)) {
488
            if (empty(static::SUPPORTED_VALUE_OBJECTS[$property['type']][static::EXTRA_TYPE_HINTS])) {
489
                return sprintf('\%s', $this->getValueObjectFullyQualifiedClassName($property));
490
            }
491
492
            return sprintf(
493
                '%s|\%s',
494
                static::SUPPORTED_VALUE_OBJECTS[$property['type']][static::EXTRA_TYPE_HINTS],
495
                $this->getValueObjectFullyQualifiedClassName($property),
496
            );
497
        }
498
499
        if ($this->isTypedArray($property)) {
500
            $type = preg_replace('/\[\]/', '', $property['type']);
501
502
            return $type . '[]';
503
        }
504
505
        if ($this->isArray($property)) {
506
            return 'array';
507
        }
508
509
        if ($this->isTypeAbstractAttributesTransfer($property)) {
510
            return '\\' . AbstractTransfer::class;
511
        }
512
513
        if ($this->isCollection($property)) {
514
            return $this->buildCollectionDocTypeHint($property);
515
        }
516
517
        if ($this->isTypeTransferObject($property)) {
518
            return '\Generated\Shared\Transfer\\' . $property['type'];
519
        }
520
521
        if ($this->isStrictProperty($property)) {
522
            return $property['type'];
523
        }
524
525
        return $property['type'] . '|null';
526
    }
527
528
    /**
529
     * @param array<string, mixed> $property
530
     *
531
     * @return string
532
     */
533
    protected function getAddVar(array $property): string
534
    {
535
        if ($this->isTypedArray($property)) {
536
            return preg_replace('/\[\]/', '', $property['type']);
537
        }
538
539
        if ($this->isArray($property)) {
540
            return 'mixed';
541
        }
542
543
        if ($this->isCollection($property)) {
544
            return '\Generated\Shared\Transfer\\' . str_replace('[]', '', $property['type']);
545
        }
546
547
        return str_replace('[]', '', $property['type']);
548
    }
549
550
    /**
551
     * @return array
552
     */
553
    public function getConstants(): array
554
    {
555
        return $this->constants;
556
    }
557
558
    /**
559
     * @return array
560
     */
561
    public function getProperties(): array
562
    {
563
        return $this->properties;
564
    }
565
566
    /**
567
     * @return array<string, string>
568
     */
569
    public function getPropertyNameMap(): array
570
    {
571
        return $this->propertyNameMap;
572
    }
573
574
    /**
575
     * @param array $properties
576
     *
577
     * @return void
578
     */
579
    protected function addMethods(array $properties): void
580
    {
581
        foreach ($properties as $property) {
582
            $this->addPropertyMethods($property);
583
        }
584
    }
585
586
    /**
587
     * @param array<string, mixed> $property
588
     *
589
     * @return void
590
     */
591
    protected function addPropertyMethods(array $property): void
592
    {
593
        $this->buildGetterAndSetter($property);
594
595
        if ($this->isCollection($property) || $this->isArray($property)) {
596
            $this->buildAddMethod($property);
597
        }
598
599
        $this->buildRequireMethod($property);
600
        $this->buildStrictPropertyMethods($property);
601
    }
602
603
    /**
604
     * @param array<string, mixed> $property
605
     *
606
     * @return void
607
     */
608
    protected function buildStrictPropertyMethods(array $property): void
609
    {
610
        if (!$this->isStrictProperty($property)) {
611
            return;
612
        }
613
614
        if ($this->isAssociativeArray($property)) {
615
            $this->buildGetCollectionElementMethod($property);
616
        }
617
    }
618
619
    /**
620
     * @return array
621
     */
622
    public function getConstructorDefinition(): array
623
    {
624
        return $this->constructorDefinition;
625
    }
626
627
    /**
628
     * @return array
629
     */
630
    public function getMethods(): array
631
    {
632
        return $this->methods;
633
    }
634
635
    /**
636
     * @return array
637
     */
638
    public function getNormalizedProperties(): array
639
    {
640
        return $this->normalizedProperties;
641
    }
642
643
    /**
644
     * @return string|null
645
     */
646
    public function getDeprecationDescription(): ?string
647
    {
648
        return $this->deprecationDescription;
649
    }
650
651
    /**
652
     * @param array<string, mixed> $property
653
     *
654
     * @return void
655
     */
656
    protected function buildGetterAndSetter(array $property): void
657
    {
658
        $this->buildSetMethod($property);
659
        $this->buildGetMethod($property);
660
661
        if ($this->isPropertyCollection($property)) {
662
            $this->buildSetOrFailMethod($property);
663
            $this->buildGetOrFailMethod($property);
664
        }
665
    }
666
667
    /**
668
     * @param array<string, mixed> $property
669
     *
670
     * @return void
671
     */
672
    protected function buildSetOrFailMethod(array $property): void
673
    {
674
        $propertyName = $this->getPropertyName($property);
675
        $defaultSetMethodName = 'set' . ucfirst($propertyName);
676
        $methodName = sprintf('%sOrFail', $defaultSetMethodName);
677
678
        $method = [
679
            'name' => $methodName,
680
            'defaultSetMethodName' => $defaultSetMethodName,
681
            'property' => $propertyName,
682
            'propertyConst' => $this->getPropertyConstantName($property),
683
            'var' => $this->buildSetArgumentType($property),
684
            'bundles' => $property['bundles'],
685
            'typeHint' => null,
686
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
687
        ];
688
        $method = $this->addSetOrFailTypeHint($method, $property);
689
690
        if ($this->propertyHasTypeShim($property)) {
691
            $method['typeShimNotice'] = $this->buildTypeShimNotice(
692
                $property['type'],
693
                $this->getPropertyTypeShim($property),
694
            );
695
        }
696
697
        $this->methods[$methodName] = $method;
698
    }
699
700
    /**
701
     * @param array<string, mixed> $property
702
     *
703
     * @return void
704
     */
705
    protected function buildGetOrFailMethod(array $property): void
706
    {
707
        $propertyName = $this->getPropertyName($property);
708
        $methodName = sprintf('get%sOrFail', ucfirst($propertyName));
709
        $method = [
710
            'name' => $methodName,
711
            'property' => $propertyName,
712
            'propertyConst' => $this->getPropertyConstantName($property),
713
            'return' => preg_replace('/\|null$/', '', $this->getReturnType($property)),
714
            'bundles' => $property['bundles'],
715
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
716
            'isAbstractAttributesTransfer' => $this->isTypeAbstractAttributesTransfer($property),
717
        ];
718
719
        $method = $this->addGetOrFailTypeHint($method, $property);
720
        $this->methods[$methodName] = $method;
721
    }
722
723
    /**
724
     * @param array<string, mixed> $property
725
     *
726
     * @return string
727
     */
728
    protected function getPropertyConstantName(array $property): string
729
    {
730
        $filter = new CamelCaseToUnderscore();
731
        /** @var string $value */
732
        $value = $filter->filter($property['name']);
733
734
        return mb_strtoupper($value);
735
    }
736
737
    /**
738
     * @param array<string, mixed> $property
739
     *
740
     * @return string
741
     */
742
    protected function getPropertyName(array $property): string
743
    {
744
        $filter = new UnderscoreToCamelCase();
745
        /** @var string $value */
746
        $value = $filter->filter($property['name']);
747
748
        return lcfirst($value);
749
    }
750
751
    /**
752
     * @param array<string, mixed> $property
753
     *
754
     * @return string
755
     */
756
    protected function getReturnType(array $property): string
757
    {
758
        if ($this->isValueObject($property)) {
759
            return sprintf('\%s|null', $this->getValueObjectFullyQualifiedClassName($property));
760
        }
761
762
        if ($this->isTypedArray($property)) {
763
            $type = preg_replace('/\[\]/', '', $property['type']);
764
765
            return $type . '[]';
766
        }
767
768
        if ($this->isPrimitiveArray($property)) {
769
            return 'array|null';
770
        }
771
772
        if ($this->isArrayCollection($property)) {
773
            return 'array';
774
        }
775
776
        if ($this->isTypeAbstractAttributesTransfer($property)) {
777
            return '\\' . AbstractTransfer::class . '|null';
778
        }
779
780
        if ($this->isCollection($property)) {
781
            return $this->buildCollectionDocTypeHint($property);
782
        }
783
784
        if ($this->isTypeTransferObject($property)) {
785
            return '\Generated\Shared\Transfer\\' . $property['type'] . '|null';
786
        }
787
788
        return $property['type'] . '|null';
789
    }
790
791
    /**
792
     * @param array<string, mixed> $property
793
     *
794
     * @return bool
795
     */
796
    protected function isCollection(array $property): bool
797
    {
798
        return (bool)preg_match('/((.*?)\[\])/', $property['type']);
799
    }
800
801
    /**
802
     * @param array<string, mixed> $property
803
     *
804
     * @return bool
805
     */
806
    protected function isPropertyCollection(array $property): bool
807
    {
808
        return !$this->isArrayCollection($property) && !$this->isCollection($property);
809
    }
810
811
    /**
812
     * @param array<string, mixed> $property
813
     *
814
     * @return bool
815
     */
816
    protected function isArray(array $property): bool
817
    {
818
        return ($property['type'] === 'array' || $property['type'] === '[]' || $this->isTypedArray($property));
819
    }
820
821
    /**
822
     * @param array<string, mixed> $property
823
     *
824
     * @return bool
825
     */
826
    protected function isPrimitiveArray(array $property): bool
827
    {
828
        if (!$this->isStrictProperty($property)) {
829
            return false;
830
        }
831
832
        return $property['type'] === 'array' && !isset($property['singular']) && !$this->isAssociativeArray($property);
833
    }
834
835
    /**
836
     * @param array<string, mixed> $property
837
     *
838
     * @return bool
839
     */
840
    protected function isArrayCollection(array $property): bool
841
    {
842
        if ($this->isStrictProperty($property)) {
843
            return $this->isArray($property) && !$this->isPrimitiveArray($property);
844
        }
845
846
        return $this->isArray($property);
847
    }
848
849
    /**
850
     * @param array<string, mixed> $property
851
     *
852
     * @return bool
853
     */
854
    protected function isAssociativeArray(array $property): bool
855
    {
856
        return isset($property['associative']) && filter_var($property['associative'], FILTER_VALIDATE_BOOLEAN);
857
    }
858
859
    /**
860
     * @param array<string, mixed> $property
861
     *
862
     * @return bool
863
     */
864
    protected function isTypedArray(array $property): bool
865
    {
866
        return (bool)preg_match('/array\[\]|callable\[\]|int\[\]|integer\[\]|float\[\]|decimal\[\]|string\[\]|bool\[\]|boolean\[\]|iterable\[\]|object\[\]|resource\[\]|mixed\[\]/', $property['type']);
867
    }
868
869
    /**
870
     * @param array<string, mixed> $property
871
     *
872
     * @return string|bool
873
     */
874
    protected function getSetTypeHint(array $property)
875
    {
876
        if ($this->isStrictProperty($property)) {
877
            return $this->getStrictSetTypeHint($property);
878
        }
879
880
        if ($this->isArray($property) && isset($property['associative'])) {
881
            return false;
882
        }
883
884
        if ($this->isArray($property)) {
885
            return 'array';
886
        }
887
888
        if ($this->isTypeAbstractAttributesTransfer($property)) {
889
            return 'AbstractTransfer';
890
        }
891
892
        if ($this->isValueObject($property)) {
893
            $this->addUseStatement($this->getValueObjectFullyQualifiedClassName($property));
894
895
            return false;
896
        }
897
898
        if (preg_match('/^(string|int|integer|float|bool|boolean)$/', $property['type'])) {
899
            return false;
900
        }
901
902
        if ($this->isCollection($property)) {
903
            $this->addUseStatement(ArrayObject::class);
904
905
            return 'ArrayObject';
906
        }
907
908
        return $property['type'];
909
    }
910
911
    /**
912
     * @param array<string, mixed> $property
913
     *
914
     * @return string|bool
915
     */
916
    protected function getAddTypeHint(array $property)
917
    {
918
        if ($this->isStrictProperty($property)) {
919
            return $this->getStrictCollectionElementTypeHint($property);
920
        }
921
922
        if (preg_match('/^(string|int|integer|float|bool|boolean|mixed|resource|callable|iterable|array|\[\])/', $property['type'])) {
923
            return false;
924
        }
925
926
        return str_replace('[]', '', $property['type']);
927
    }
928
929
    /**
930
     * @param array<string, mixed> $property
931
     *
932
     * @return string|bool
933
     */
934
    protected function getStrictCollectionElementTypeHint(array $property)
935
    {
936
        if ($property['type'] === 'array' || $property['type'] === 'mixed') {
937
            return false;
938
        }
939
940
        return str_replace('[]', '', $property['type']);
941
    }
942
943
    /**
944
     * @param array<string, mixed> $property
945
     *
946
     * @return void
947
     */
948
    protected function buildGetMethod(array $property): void
949
    {
950
        $propertyName = $this->getPropertyName($property);
951
        $methodName = 'get' . ucfirst($propertyName);
952
        $method = [
953
            'name' => $methodName,
954
            'property' => $propertyName,
955
            'propertyConst' => $this->getPropertyConstantName($property),
956
            'return' => $this->buildGetReturnTypeData($property),
957
            'bundles' => $property['bundles'],
958
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
959
            'isAbstractAttributesTransfer' => $this->isTypeAbstractAttributesTransfer($property),
960
        ];
961
962
        if ($this->propertyHasTypeShim($property)) {
963
            $method['typeShimNotice'] = $this->buildTypeShimNotice(
964
                $property['type'],
965
                $this->getPropertyTypeShim($property),
966
            );
967
        }
968
969
        $method = $this->addGetReturnTypeHint($method, $property);
970
971
        $this->methods[$methodName] = $method;
972
    }
973
974
    /**
975
     * @param array<string, mixed> $property
976
     *
977
     * @return void
978
     */
979
    protected function buildSetMethod(array $property): void
980
    {
981
        $propertyName = $this->getPropertyName($property);
982
        $methodName = 'set' . ucfirst($propertyName);
983
        $method = [
984
            'name' => $methodName,
985
            'property' => $propertyName,
986
            'propertyConst' => $this->getPropertyConstantName($property),
987
            'var' => $this->buildSetArgumentType($property),
988
            'valueObject' => false,
989
            'bundles' => $property['bundles'],
990
            'typeHint' => null,
991
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
992
            'isAbstractAttributesTransfer' => $this->isTypeAbstractAttributesTransfer($property),
993
        ];
994
        $method = $this->addSetTypeHint($method, $property);
995
        $method = $this->addDefaultNull($method, $property);
996
        $method = $this->setTypeAssertionMode($method);
997
998
        if ($this->isArrayCollection($property)) {
999
            $method['setsArrayCollection'] = true;
1000
        }
1001
1002
        if ($this->isTypeAbstractAttributesTransfer($property)) {
1003
            $method['attributesTransfer'] = true;
1004
            $method['abstractAttributesTransfer'] = 'AbstractAttributesTransfer';
1005
        }
1006
1007
        if ($this->isCollectionPropertyTypeCheckNeeded($property)) {
1008
            $method['isCollectionPropertyTypeCheckNeeded'] = true;
1009
            $method['addMethodName'] = 'add' . ucfirst($this->getPropertySingularName($property));
1010
        }
1011
1012
        if ($this->propertyHasTypeShim($property)) {
1013
            $method['typeShimNotice'] = $this->buildTypeShimNotice(
1014
                $property['type'],
1015
                $this->getPropertyTypeShim($property),
1016
            );
1017
        }
1018
1019
        if ($this->isValueObject($property)) {
1020
            $method['valueObject'] = $this->getShortClassName(
1021
                $this->getValueObjectFullyQualifiedClassName($property),
1022
            );
1023
        }
1024
1025
        $this->methods[$methodName] = $method;
1026
    }
1027
1028
    /**
1029
     * @param array<string, mixed> $property
1030
     *
1031
     * @return void
1032
     */
1033
    protected function buildAddMethod(array $property): void
1034
    {
1035
        $parent = $this->getPropertyName($property);
1036
        $propertyConstant = $this->getPropertyConstantName($property);
1037
        $propertyName = $this->getPropertySingularName($property);
1038
        $methodName = 'add' . ucfirst($propertyName);
1039
1040
        $method = [
1041
            'name' => $methodName,
1042
            'property' => $propertyName,
1043
            'propertyConst' => $propertyConstant,
1044
            'parent' => $parent,
1045
            'var' => $this->buildAddArgumentType($property),
1046
            'bundles' => $property['bundles'],
1047
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
1048
            'is_associative' => $this->isAssociativeArray($property),
1049
        ];
1050
1051
        if ($this->propertyHasTypeShim($property)) {
1052
            $method['typeShimNotice'] = $this->buildAddTypeShimNotice(
1053
                $property['type'],
1054
                $this->getPropertyTypeShim($property),
0 ignored issues
show
Bug introduced by
It seems like $this->getPropertyTypeShim($property) can also be of type null; however, parameter $typeShim of Spryker\Zed\Transfer\Bus...uildAddTypeShimNotice() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1054
                /** @scrutinizer ignore-type */ $this->getPropertyTypeShim($property),
Loading history...
1055
            );
1056
        }
1057
1058
        $typeHint = $this->getAddTypeHint($property);
1059
        if ($typeHint) {
1060
            $method['typeHint'] = $typeHint;
1061
        }
1062
1063
        if ($method['is_associative']) {
1064
            $method['var'] = static::DEFAULT_ASSOCIATIVE_ARRAY_TYPE;
1065
            $method['typeHint'] = null;
1066
            $method['varValue'] = $this->buildAddArgumentType($property);
1067
            $method['typeHintValue'] = $this->getAddTypeHint($property);
1068
        }
1069
1070
        $method = $this->setTypeAssertionMode($method);
1071
        $this->methods[$methodName] = $method;
1072
    }
1073
1074
    /**
1075
     * @param array<string, mixed> $method
1076
     * @param array<string, mixed> $property
1077
     *
1078
     * @return array<string, mixed>
1079
     */
1080
    protected function addSetTypeHint(array $method, array $property): array
1081
    {
1082
        $typeHint = $this->getSetTypeHint($property);
1083
1084
        if ($typeHint) {
1085
            $method['typeHint'] = $typeHint;
1086
        }
1087
1088
        return $method;
1089
    }
1090
1091
    /**
1092
     * @param array<string, mixed> $method
1093
     * @param array<string, mixed> $property
1094
     *
1095
     * @return array<string, mixed>
1096
     */
1097
    protected function addDefaultNull(array $method, array $property): array
1098
    {
1099
        $method['hasDefaultNull'] = false;
1100
1101
        if ($this->isValueObject($property) || ($method['typeHint'] && (!$this->isCollection($property) || $method['typeHint'] === 'array'))) {
1102
            $method['hasDefaultNull'] = true;
1103
        }
1104
1105
        return $method;
1106
    }
1107
1108
    /**
1109
     * @param array<string, mixed> $property
1110
     *
1111
     * @return void
1112
     */
1113
    protected function buildRequireMethod(array $property): void
1114
    {
1115
        $propertyName = $this->getPropertyName($property);
1116
        $methodName = 'require' . ucfirst($propertyName);
1117
        $method = [
1118
            'name' => $methodName,
1119
            'property' => $propertyName,
1120
            'propertyConst' => $this->getPropertyConstantName($property),
1121
            'isCollection' => ($this->isCollection($property) && !$this->isArray($property)),
1122
            'bundles' => $property['bundles'],
1123
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
1124
        ];
1125
        $this->methods[$methodName] = $method;
1126
    }
1127
1128
    /**
1129
     * @param array<string, mixed> $property
1130
     *
1131
     * @return bool
1132
     */
1133
    protected function isStrictProperty(array $property): bool
1134
    {
1135
        return $property[DefinitionNormalizer::KEY_STRICT_MODE] ?? false;
1136
    }
1137
1138
    /**
1139
     * @param array<string, mixed> $property
1140
     *
1141
     * @return void
1142
     */
1143
    protected function assertProperty(array $property): void
1144
    {
1145
        $this->assertPropertyName($property['name']);
1146
        $this->assertPropertyAssociative($property);
1147
    }
1148
1149
    /**
1150
     * @param string $propertyName
1151
     *
1152
     * @throws \Spryker\Zed\Transfer\Business\Exception\InvalidNameException
1153
     *
1154
     * @return void
1155
     */
1156
    protected function assertPropertyName($propertyName): void
1157
    {
1158
        if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]+$/', $propertyName)) {
1159
            throw new InvalidNameException(sprintf(
1160
                'Transfer property "%s" needs to be alpha-numeric and camel-case formatted in "%s"!',
1161
                $propertyName,
1162
                $this->name,
1163
            ));
1164
        }
1165
    }
1166
1167
    /**
1168
     * @param array<string, mixed> $property
1169
     *
1170
     * @return void
1171
     */
1172
    protected function assertPropertyAssociative(array $property): void
1173
    {
1174
        if (isset($property['associative'])) {
1175
            $this->assertPropertyAssociativeType($property);
1176
            $this->assertPropertyAssociativeValue($property);
1177
        }
1178
    }
1179
1180
    /**
1181
     * @param array<string, mixed> $property
1182
     *
1183
     * @throws \Spryker\Zed\Transfer\Business\Exception\InvalidAssociativeValueException
1184
     *
1185
     * @return void
1186
     */
1187
    protected function assertPropertyAssociativeValue(array $property): void
1188
    {
1189
        if (!preg_match('(true|false|1|0)', $property['associative'])) {
1190
            throw new InvalidAssociativeValueException(
1191
                'Transfer property "associative" has invalid value. The value has to be "true" or "false".',
1192
            );
1193
        }
1194
    }
1195
1196
    /**
1197
     * @param array<string, mixed> $property
1198
     *
1199
     * @throws \Spryker\Zed\Transfer\Business\Exception\InvalidAssociativeTypeException
1200
     *
1201
     * @return void
1202
     */
1203
    protected function assertPropertyAssociativeType(array $property): void
1204
    {
1205
        if (!$this->isArray($property) && !$this->isCollection($property)) {
1206
            throw new InvalidAssociativeTypeException(sprintf(
1207
                'Transfer property "associative" cannot be defined to type: "%s"!',
1208
                $property['type'],
1209
            ));
1210
        }
1211
    }
1212
1213
    /**
1214
     * @param array<string, mixed> $property
1215
     *
1216
     * @return string|null
1217
     */
1218
    protected function getPropertyDeprecationDescription(array $property): ?string
1219
    {
1220
        return $property['deprecated'] ?? null;
1221
    }
1222
1223
    /**
1224
     * @param array<string, mixed> $definition
1225
     *
1226
     * @return void
1227
     */
1228
    protected function addEntityNamespace(array $definition): void
1229
    {
1230
        if (isset($definition['entity-namespace'])) {
1231
            $this->entityNamespace = $definition['entity-namespace'];
1232
        }
1233
    }
1234
1235
    /**
1236
     * @return string|null
1237
     */
1238
    public function getEntityNamespace(): ?string
1239
    {
1240
        return $this->entityNamespace;
1241
    }
1242
1243
    /**
1244
     * @param string $name
1245
     *
1246
     * @throws \Spryker\Zed\Transfer\Business\Exception\InvalidNameException
1247
     *
1248
     * @return void
1249
     */
1250
    protected function assertValidName(string $name): void
1251
    {
1252
        if (preg_match('/Transfer$/', $name)) {
1253
            throw new InvalidNameException(sprintf(
1254
                'Transfer names must not be suffixed with the word "Transfer", it will be auto-appended on generation: `%s`. Please remove the suffix.',
1255
                $name,
1256
            ));
1257
        }
1258
    }
1259
1260
    /**
1261
     * @param array<string, mixed> $property
1262
     *
1263
     * @return string
1264
     */
1265
    public function getValueObjectFullyQualifiedClassName(array $property): string
1266
    {
1267
        return static::SUPPORTED_VALUE_OBJECTS[$property['type']][static::TYPE_FULLY_QUALIFIED];
1268
    }
1269
1270
    /**
1271
     * @return bool
1272
     */
1273
    public function isDebugMode(): bool
1274
    {
1275
        return $this->transferConfig->isDebugEnabled();
1276
    }
1277
1278
    /**
1279
     * @param string $fullyQualifiedClassName
1280
     *
1281
     * @return string
1282
     */
1283
    protected function getShortClassName(string $fullyQualifiedClassName): string
1284
    {
1285
        return substr((string)strrchr($fullyQualifiedClassName, '\\'), 1);
1286
    }
1287
1288
    /**
1289
     * @param string $fullyQualifiedClassName
1290
     *
1291
     * @return void
1292
     */
1293
    protected function addUseStatement(string $fullyQualifiedClassName): void
1294
    {
1295
        if (isset($this->useStatements[$fullyQualifiedClassName])) {
1296
            return;
1297
        }
1298
1299
        $this->useStatements[$fullyQualifiedClassName] = $fullyQualifiedClassName;
1300
        ksort($this->useStatements, SORT_STRING);
1301
    }
1302
1303
    /**
1304
     * @param array<string, mixed> $transferDefinition
1305
     *
1306
     * @return array<string, mixed>
1307
     */
1308
    protected function shimTransferDefinitionPropertyTypes(array $transferDefinition): array
1309
    {
1310
        $transferName = $transferDefinition['name'];
1311
        $shim = $this->transferConfig->getTypeShims()[$transferName] ?? null;
1312
1313
        if (!isset($transferDefinition['property']) || !$shim) {
1314
            return $transferDefinition;
1315
        }
1316
1317
        foreach ($shim as $propertyName => $shimChange) {
1318
            foreach ($transferDefinition['property'] as $propertyKey => $propertyDefinition) {
1319
                if ($propertyDefinition['name'] !== $propertyName) {
1320
                    continue;
1321
                }
1322
1323
                $propertyDefinition = $this->shimPropertyType($propertyDefinition, $shimChange);
1324
                $transferDefinition['property'][$propertyKey] = $propertyDefinition;
1325
            }
1326
        }
1327
1328
        return $transferDefinition;
1329
    }
1330
1331
    /**
1332
     * @param array<string, mixed> $propertyDefinition
1333
     * @param array<string> $shimChange
1334
     *
1335
     * @return array
1336
     */
1337
    protected function shimPropertyType(array $propertyDefinition, array $shimChange): array
1338
    {
1339
        $toType = $shimChange[$propertyDefinition['type']] ?? null;
1340
1341
        if ($toType !== null) {
1342
            $propertyDefinition['typeShim'] = $toType;
1343
        }
1344
1345
        return $propertyDefinition;
1346
    }
1347
1348
    /**
1349
     * @param array<string, mixed> $property
1350
     *
1351
     * @return string|null
1352
     */
1353
    protected function getPropertyTypeShim(array $property): ?string
1354
    {
1355
        return $property['typeShim'] ?? null;
1356
    }
1357
1358
    /**
1359
     * @param array<string, mixed> $property
1360
     *
1361
     * @return bool
1362
     */
1363
    protected function propertyHasTypeShim(array $property): bool
1364
    {
1365
        return (bool)$this->getPropertyTypeShim($property);
1366
    }
1367
1368
    /**
1369
     * @param array<string, mixed> $property
1370
     *
1371
     * @return string
1372
     */
1373
    protected function buildPropertyType(array $property): string
1374
    {
1375
        return $this->buildType(
1376
            $this->getPropertyType($property),
1377
            $this->getPropertyTypeShim($property),
1378
        );
1379
    }
1380
1381
    /**
1382
     * @param array<string, mixed> $property
1383
     *
1384
     * @return string
1385
     */
1386
    protected function buildSetArgumentType(array $property): string
1387
    {
1388
        return $this->buildType(
1389
            $this->getSetVar($property),
1390
            $this->getPropertyTypeShim($property),
1391
        );
1392
    }
1393
1394
    /**
1395
     * @param array<string, mixed> $property
1396
     *
1397
     * @return string
1398
     */
1399
    protected function buildGetReturnTypeData(array $property): string
1400
    {
1401
        return $this->buildType(
1402
            $this->getReturnType($property),
1403
            $this->getPropertyTypeShim($property),
1404
        );
1405
    }
1406
1407
    /**
1408
     * @param array<string, mixed> $property
1409
     *
1410
     * @return string
1411
     */
1412
    protected function buildAddArgumentType(array $property): string
1413
    {
1414
        $type = $this->getAddVar($property);
1415
        $typeShim = $this->getPropertyTypeShim($property);
1416
1417
        if ($typeShim !== null) {
1418
            $typeShim = str_replace('[]', '', $typeShim);
1419
        }
1420
1421
        return $this->buildType($type, $typeShim);
1422
    }
1423
1424
    /**
1425
     * @param string $type
1426
     * @param string|null $typeShim
1427
     *
1428
     * @return string
1429
     */
1430
    protected function buildType(string $type, ?string $typeShim = null): string
1431
    {
1432
        if ($typeShim === null) {
1433
            return $type;
1434
        }
1435
1436
        return sprintf('%s|%s', $typeShim, $type);
1437
    }
1438
1439
    /**
1440
     * @param string $type
1441
     * @param string $typeShim
1442
     *
1443
     * @return string
1444
     */
1445
    protected function buildAddTypeShimNotice(string $type, string $typeShim): string
1446
    {
1447
        $type = str_replace('[]', '', $type);
1448
        $typeShim = str_replace('[]', '', $typeShim);
1449
1450
        return $this->buildTypeShimNotice($type, $typeShim);
1451
    }
1452
1453
    /**
1454
     * @param string $type
1455
     * @param string|null $typeShim
1456
     *
1457
     * @return string
1458
     */
1459
    protected function buildTypeShimNotice(string $type, ?string $typeShim): string
1460
    {
1461
        return sprintf(static::SHIM_NOTICE_TEMPLATE, $typeShim, $type);
1462
    }
1463
1464
    /**
1465
     * @param array<string, mixed> $method
1466
     *
1467
     * @return array<string, mixed>
1468
     */
1469
    protected function setTypeAssertionMode(array $method): array
1470
    {
1471
        $method['isTypeAssertionEnabled'] = false;
1472
        $methodArgumentType = $method['varValue'] ?? $method['var'];
1473
1474
        if (!$this->isDebugMode() || $this->getEntityNamespace() || $methodArgumentType === 'mixed') {
1475
            return $method;
1476
        }
1477
1478
        $methodArgumentTypeHint = $method['typeHintValue'] ?? $method['typeHint'] ?? null;
1479
        $method['isTypeAssertionEnabled'] = !$methodArgumentTypeHint || $methodArgumentTypeHint === 'array';
1480
1481
        return $method;
1482
    }
1483
1484
    /**
1485
     * @return void
1486
     */
1487
    protected function addExtraUseStatements(): void
1488
    {
1489
        if ($this->isDebugMode() && !$this->getEntityNamespace()) {
1490
            $this->addUseStatement(TransferTypeValidatorTrait::class);
1491
        }
1492
    }
1493
1494
    /**
1495
     * @param array<string, mixed> $method
1496
     * @param array<string, mixed> $property
1497
     *
1498
     * @return array<string, mixed>
1499
     */
1500
    protected function addGetReturnTypeHint(array $method, array $property): array
1501
    {
1502
        if ($this->isStrictProperty($property)) {
1503
            $method['returnTypeHint'] = $this->buildGetReturnTypeHint($property);
1504
        }
1505
1506
        return $method;
1507
    }
1508
1509
    /**
1510
     * @param array<string, mixed> $method
1511
     * @param array<string, mixed> $property
1512
     *
1513
     * @return array<string, mixed>
1514
     */
1515
    protected function addGetOrFailTypeHint(array $method, array $property): array
1516
    {
1517
        if ($this->isStrictProperty($property)) {
1518
            $method['returnTypeHint'] = ltrim($this->buildGetReturnTypeHint($property), '?');
1519
        }
1520
1521
        return $method;
1522
    }
1523
1524
    /**
1525
     * @param array<string, mixed> $property
1526
     *
1527
     * @return string
1528
     */
1529
    protected function buildGetReturnTypeHint(array $property): string
1530
    {
1531
        if ($this->isPrimitiveArray($property)) {
1532
            return '?array';
1533
        }
1534
1535
        if ($this->isArrayCollection($property)) {
1536
            return 'array';
1537
        }
1538
1539
        if ($this->isCollection($property)) {
1540
            return 'ArrayObject';
1541
        }
1542
1543
        $type = $property['type'];
1544
1545
        if ($this->isValueObject($property)) {
1546
            $type = $this->getShortClassName(
1547
                $this->getValueObjectFullyQualifiedClassName($property),
1548
            );
1549
        }
1550
1551
        return sprintf('?%s', $type);
1552
    }
1553
1554
    /**
1555
     * @param array<string, mixed> $method
1556
     * @param array<string, mixed> $property
1557
     *
1558
     * @return array<string, mixed>
1559
     */
1560
    protected function addSetOrFailTypeHint(array $method, array $property): array
1561
    {
1562
        $typeHint = $this->getSetTypeHint($property);
1563
1564
        if ($typeHint && is_string($typeHint)) {
1565
            $method['typeHint'] = ltrim($typeHint, '?');
1566
        }
1567
1568
        return $method;
1569
    }
1570
1571
    /**
1572
     * @param array<string, mixed> $property
1573
     *
1574
     * @return string|bool
1575
     */
1576
    protected function getStrictSetTypeHint(array $property)
1577
    {
1578
        if ($this->isPrimitiveArray($property)) {
1579
            return '?array';
1580
        }
1581
1582
        if ($this->isArrayCollection($property)) {
1583
            return 'array';
1584
        }
1585
1586
        if ($this->isValueObject($property)) {
1587
            $this->addUseStatement($this->getValueObjectFullyQualifiedClassName($property));
1588
1589
            return false;
1590
        }
1591
1592
        if ($this->isCollection($property)) {
1593
            $this->addUseStatement(ArrayObject::class);
1594
1595
            return 'ArrayObject';
1596
        }
1597
1598
        return sprintf('?%s', $property['type']);
1599
    }
1600
1601
    /**
1602
     * @param array<string, mixed> $property
1603
     *
1604
     * @return void
1605
     */
1606
    protected function buildGetCollectionElementMethod(array $property): void
1607
    {
1608
        $this->assertSingularPropertyNameIsValid($property);
1609
1610
        $originalPropertyName = $this->getPropertyName($property);
1611
        $property['name'] = $property['singular'];
1612
        $singularPropertyName = $this->getPropertyName($property);
1613
        $methodName = 'get' . ucfirst($singularPropertyName);
1614
1615
        $method = [
1616
            'name' => $methodName,
1617
            'property' => $originalPropertyName,
1618
            'var' => static::DEFAULT_ASSOCIATIVE_ARRAY_TYPE,
1619
            'bundles' => $property['bundles'],
1620
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
1621
            'return' => $this->getGetCollectionElementReturnType($property),
1622
            'isItemGetter' => true,
1623
        ];
1624
1625
        $typeHint = $this->getStrictCollectionElementTypeHint($property);
1626
        if ($typeHint) {
1627
            $method['returnTypeHint'] = $typeHint;
1628
        }
1629
1630
        $this->methods[$methodName] = $method;
1631
    }
1632
1633
    /**
1634
     * @param array<string, mixed> $property
1635
     *
1636
     * @return string
1637
     */
1638
    protected function getGetCollectionElementReturnType(array $property): string
1639
    {
1640
        /** @var string $propertyReturnType */
1641
        $propertyReturnType = str_replace('[]', '', $property['type']);
1642
1643
        if ($propertyReturnType === 'array') {
1644
            return 'mixed';
1645
        }
1646
1647
        if ($this->isCollection($property)) {
1648
            return '\Generated\Shared\Transfer\\' . $propertyReturnType;
1649
        }
1650
1651
        return $propertyReturnType;
1652
    }
1653
1654
    /**
1655
     * @param array<string, mixed> $property
1656
     *
1657
     * @throws \Spryker\Zed\Transfer\Business\Exception\InvalidSingularPropertyNameException
1658
     *
1659
     * @return void
1660
     */
1661
    protected function assertSingularPropertyNameIsValid(array $property): void
1662
    {
1663
        if ($this->isStrictProperty($property) && $this->isAssociativeArray($property) && !isset($property['singular'])) {
1664
            throw new InvalidSingularPropertyNameException(
1665
                sprintf(
1666
                    'No singular form for the property %s.%s is found. Please add "singular" attribute to this property\'s definition.',
1667
                    $this->name,
1668
                    $property['name'],
1669
                ),
1670
            );
1671
        }
1672
1673
        if ($this->isStrictProperty($property) && $this->isAssociativeArray($property) && $property['name'] === $property['singular']) {
1674
            throw new InvalidSingularPropertyNameException(
1675
                sprintf(
1676
                    'Values of the "name" and "singular" attributes of the property %s.%s must not match.',
1677
                    $this->name,
1678
                    $property['name'],
1679
                ),
1680
            );
1681
        }
1682
    }
1683
1684
    /**
1685
     * @param array<string, mixed> $property
1686
     *
1687
     * @return string
1688
     */
1689
    protected function getPropertySingularName(array $property): string
1690
    {
1691
        $property['name'] = $property['singular'] ?? $property['name'];
1692
1693
        return $this->getPropertyName($property);
1694
    }
1695
1696
    /**
1697
     * @param array<string, mixed> $property
1698
     *
1699
     * @return bool
1700
     */
1701
    protected function isCollectionPropertyTypeCheckNeeded(array $property): bool
1702
    {
1703
        return $this->isStrictProperty($property) && $this->isCollection($property);
1704
    }
1705
1706
    /**
1707
     * @param array<string, mixed> $properties
1708
     *
1709
     * @return void
1710
     */
1711
    protected function addAbstractAttributesTransferUseStatement(array $properties): void
1712
    {
1713
        foreach ($properties as $property) {
1714
            if ($property['type'] === 'AbstractAttributes') {
1715
                $this->addUseStatement(AbstractAttributesTransfer::class);
1716
1717
                return;
1718
            }
1719
        }
1720
    }
1721
1722
    /**
1723
     * @param array<string, mixed> $property
1724
     *
1725
     * @throws \Spryker\Zed\Transfer\Business\Exception\InvalidAbstractAttributesUsageException
1726
     *
1727
     * @return void
1728
     */
1729
    protected function assertAbstractAttributesTransferType(array $property): void
1730
    {
1731
        if ($property['type'] === 'AbstractAttributesTransfer[]') {
1732
            throw new InvalidAbstractAttributesUsageException('AbstractAttributes type cannot be used as array.');
1733
        }
1734
    }
1735
1736
    /**
1737
     * @param array $property
1738
     *
1739
     * @return string
1740
     */
1741
    protected function buildCollectionDocTypeHint(array $property): string
1742
    {
1743
        return sprintf('\ArrayObject<\Generated\Shared\Transfer\%s>', rtrim($property['type'], '[]'));
1744
    }
1745
}
1746