ClassDefinition::addProperties()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 5
rs 10
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
        $shouldAddSuffix = $this->transferConfig->isTransferSuffixCheckStrict()
181
            ? !str_ends_with($name, 'Transfer')
182
            : strpos($name, 'Transfer') === false;
183
184
        if ($shouldAddSuffix) {
185
            $name .= 'Transfer';
186
        }
187
188
        $this->name = ucfirst($name);
189
190
        return $this;
191
    }
192
193
    /**
194
     * @return string
195
     */
196
    public function getName(): string
197
    {
198
        return $this->name;
199
    }
200
201
    /**
202
     * @return array<string>
203
     */
204
    public function getUseStatements(): array
205
    {
206
        return $this->useStatements;
207
    }
208
209
    /**
210
     * @param array $properties
211
     *
212
     * @return void
213
     */
214
    protected function addConstants(array $properties): void
215
    {
216
        foreach ($properties as $property) {
217
            $this->addConstant($property);
218
        }
219
    }
220
221
    /**
222
     * @param array<string, mixed> $property
223
     *
224
     * @return void
225
     */
226
    protected function addConstant(array $property): void
227
    {
228
        $property['name'] = lcfirst($property['name']);
229
        $propertyInfo = [
230
            'name' => $this->getPropertyConstantName($property),
231
            'value' => $property['name'],
232
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
233
        ];
234
235
        $this->constants[$property['name']] = $propertyInfo;
236
    }
237
238
    /**
239
     * @param array $properties
240
     *
241
     * @return void
242
     */
243
    protected function addProperties(array $properties): void
244
    {
245
        foreach ($properties as $property) {
246
            $this->assertAbstractAttributesTransferType($property);
247
            $this->addProperty($property);
248
        }
249
    }
250
251
    /**
252
     * @param array<string, mixed> $property
253
     *
254
     * @return void
255
     */
256
    protected function addProperty(array $property): void
257
    {
258
        $property['name'] = lcfirst($property['name']);
259
        $propertyInfo = [
260
            'name' => $property['name'],
261
            'type' => $this->buildPropertyType($property),
262
            'bundles' => $property['bundles'],
263
            'is_associative' => $property['is_associative'],
264
            'is_array_collection' => $this->isArrayCollection($property),
265
        ];
266
267
        $this->properties[$property['name']] = $propertyInfo;
268
    }
269
270
    /**
271
     * @param array $properties
272
     *
273
     * @return void
274
     */
275
    protected function setPropertyNameMap(array $properties): void
276
    {
277
        /** @var array<string, string> $property */
278
        foreach ($properties as $property) {
279
            $nameCamelCase = $this->getPropertyName($property);
280
            $this->propertyNameMap[$property['name_underscore']] = $nameCamelCase;
281
            $this->propertyNameMap[$nameCamelCase] = $nameCamelCase;
282
            $this->propertyNameMap[ucfirst($nameCamelCase)] = $nameCamelCase;
283
        }
284
    }
285
286
    /**
287
     * Properties which are Transfer MUST be suffixed with Transfer
288
     *
289
     * @param array $properties
290
     *
291
     * @return array
292
     */
293
    protected function normalizePropertyTypes(array $properties): array
294
    {
295
        $normalizedProperties = [];
296
        foreach ($properties as $property) {
297
            $this->assertProperty($property);
298
299
            $property[static::TYPE_FULLY_QUALIFIED] = $property['type'];
300
            $property['is_collection'] = false;
301
            $property['is_primitive_array'] = false;
302
            $property['is_transfer'] = false;
303
            $property['is_value_object'] = false;
304
            $property['propertyConst'] = $this->getPropertyConstantName($property);
305
            $property['name_underscore'] = mb_strtolower($property['propertyConst']);
306
307
            if ($this->isTransferOrTransferArray($property['type'])) {
308
                $property = $this->buildTransferPropertyDefinition($property);
309
            }
310
311
            if ($this->isArray($property) && $this->transferConfig->isArrayRequireValidationEnabled()) {
312
                $property = $this->buildArrayPropertyDefinition($property);
313
            }
314
315
            if ($this->isValueObject($property)) {
316
                $property = $this->buildValueObjectPropertyDefinition($property);
317
            }
318
            if ($this->isAbstractAttributesTransfer($property['type'])) {
319
                $property = $this->buildAbstractAttributesTransferPropertyDefinition($property);
320
            }
321
322
            $property['is_associative'] = $this->isAssociativeArray($property);
323
            $property['is_strict'] = $this->isStrictProperty($property);
324
325
            $normalizedProperties[] = $property;
326
        }
327
328
        $this->normalizedProperties = $normalizedProperties;
329
330
        return $normalizedProperties;
331
    }
332
333
    /**
334
     * @param array<string, mixed> $property
335
     *
336
     * @return array
337
     */
338
    protected function buildTransferPropertyDefinition(array $property): array
339
    {
340
        $property['is_transfer'] = true;
341
        $property[static::TYPE_FULLY_QUALIFIED] = 'Generated\\Shared\\Transfer\\';
342
343
        if (preg_match('/\[\]$/', $property['type'])) {
344
            $property['type'] = str_replace('[]', '', $property['type']) . 'Transfer[]';
345
            $property[static::TYPE_FULLY_QUALIFIED] = 'Generated\\Shared\\Transfer\\' . str_replace('[]', '', $property['type']);
346
            $property['is_collection'] = true;
347
348
            return $property;
349
        }
350
        $property['type'] .= 'Transfer';
351
        $property[static::TYPE_FULLY_QUALIFIED] .= $property['type'];
352
353
        return $property;
354
    }
355
356
    /**
357
     * @param array<string, mixed> $property
358
     *
359
     * @return array
360
     */
361
    protected function buildArrayPropertyDefinition(array $property): array
362
    {
363
        $property['is_primitive_array'] = true;
364
365
        return $property;
366
    }
367
368
    /**
369
     * @param array<string, mixed> $property
370
     *
371
     * @return array
372
     */
373
    protected function buildAbstractAttributesTransferPropertyDefinition(array $property): array
374
    {
375
        $property['is_transfer'] = true;
376
        $property[static::TYPE_FULLY_QUALIFIED] = AbstractAttributesTransfer::class;
377
378
        return $property;
379
    }
380
381
    /**
382
     * @param array<string, mixed> $property
383
     *
384
     * @return array
385
     */
386
    protected function buildValueObjectPropertyDefinition(array $property): array
387
    {
388
        $property['is_value_object'] = true;
389
        $property[static::TYPE_FULLY_QUALIFIED] = $this->getValueObjectFullyQualifiedClassName($property);
390
391
        return $property;
392
    }
393
394
    /**
395
     * @param string $type
396
     *
397
     * @return bool
398
     */
399
    protected function isTransferOrTransferArray($type): bool
400
    {
401
        return (bool)preg_match('/^[A-Z].*/', $type);
402
    }
403
404
    /**
405
     * @param string $type
406
     *
407
     * @return bool
408
     */
409
    protected function isAbstractAttributesTransfer(string $type): bool
410
    {
411
        return $type === 'AbstractAttributesTransfer';
412
    }
413
414
    /**
415
     * @param array<string, mixed> $property
416
     *
417
     * @return bool
418
     */
419
    protected function isValueObject(array $property): bool
420
    {
421
        return isset(static::SUPPORTED_VALUE_OBJECTS[$property['type']]);
422
    }
423
424
    /**
425
     * @param array<string, mixed> $property
426
     *
427
     * @return string
428
     */
429
    protected function getPropertyType(array $property): string
430
    {
431
        if ($this->isValueObject($property)) {
432
            return sprintf('\%s|null', $this->getValueObjectFullyQualifiedClassName($property));
433
        }
434
435
        if ($this->isTypedArray($property)) {
436
            $type = preg_replace('/\[\]/', '', $property['type']);
437
438
            return $type . '[]';
439
        }
440
441
        if ($this->isPrimitiveArray($property)) {
442
            return 'array|null';
443
        }
444
445
        if ($this->isArrayCollection($property)) {
446
            return 'array';
447
        }
448
449
        if ($this->isTypeAbstractAttributesTransfer($property)) {
450
            return sprintf('\%s|\null', AbstractTransfer::class);
451
        }
452
453
        if ($this->isCollection($property)) {
454
            return $this->buildCollectionDocTypeHint($property);
455
        }
456
457
        if ($this->isTypeTransferObject($property)) {
458
            return '\Generated\Shared\Transfer\\' . $property['type'] . '|null';
459
        }
460
461
        return $property['type'] . '|null';
462
    }
463
464
    /**
465
     * @param array<string, mixed> $property
466
     *
467
     * @return bool
468
     */
469
    protected function isTypeTransferObject(array $property): bool
470
    {
471
        return ($property['is_transfer']);
472
    }
473
474
    /**
475
     * @param array<string, mixed> $property
476
     *
477
     * @return bool
478
     */
479
    protected function isTypeAbstractAttributesTransfer(array $property): bool
480
    {
481
        return ($property['is_transfer'] && $property[static::TYPE_FULLY_QUALIFIED] === AbstractAttributesTransfer::class);
482
    }
483
484
    /**
485
     * @param array<string, mixed> $property
486
     *
487
     * @return string
488
     */
489
    protected function getSetVar(array $property): string
490
    {
491
        if ($this->isValueObject($property)) {
492
            if (empty(static::SUPPORTED_VALUE_OBJECTS[$property['type']][static::EXTRA_TYPE_HINTS])) {
493
                return sprintf('\%s', $this->getValueObjectFullyQualifiedClassName($property));
494
            }
495
496
            return sprintf(
497
                '%s|\%s',
498
                static::SUPPORTED_VALUE_OBJECTS[$property['type']][static::EXTRA_TYPE_HINTS],
499
                $this->getValueObjectFullyQualifiedClassName($property),
500
            );
501
        }
502
503
        if ($this->isTypedArray($property)) {
504
            $type = preg_replace('/\[\]/', '', $property['type']);
505
506
            return $type . '[]';
507
        }
508
509
        if ($this->isArray($property)) {
510
            return 'array';
511
        }
512
513
        if ($this->isTypeAbstractAttributesTransfer($property)) {
514
            return '\\' . AbstractTransfer::class;
515
        }
516
517
        if ($this->isCollection($property)) {
518
            return $this->buildCollectionDocTypeHint($property);
519
        }
520
521
        if ($this->isTypeTransferObject($property)) {
522
            return '\Generated\Shared\Transfer\\' . $property['type'];
523
        }
524
525
        if ($this->isStrictProperty($property)) {
526
            return $property['type'];
527
        }
528
529
        return $property['type'] . '|null';
530
    }
531
532
    /**
533
     * @param array<string, mixed> $property
534
     *
535
     * @return string
536
     */
537
    protected function getAddVar(array $property): string
538
    {
539
        if ($this->isTypedArray($property)) {
540
            return preg_replace('/\[\]/', '', $property['type']);
541
        }
542
543
        if ($this->isArray($property)) {
544
            return 'mixed';
545
        }
546
547
        if ($this->isCollection($property)) {
548
            return '\Generated\Shared\Transfer\\' . str_replace('[]', '', $property['type']);
549
        }
550
551
        return str_replace('[]', '', $property['type']);
552
    }
553
554
    /**
555
     * @return array
556
     */
557
    public function getConstants(): array
558
    {
559
        return $this->constants;
560
    }
561
562
    /**
563
     * @return array
564
     */
565
    public function getProperties(): array
566
    {
567
        return $this->properties;
568
    }
569
570
    /**
571
     * @return array<string, string>
572
     */
573
    public function getPropertyNameMap(): array
574
    {
575
        return $this->propertyNameMap;
576
    }
577
578
    /**
579
     * @param array $properties
580
     *
581
     * @return void
582
     */
583
    protected function addMethods(array $properties): void
584
    {
585
        foreach ($properties as $property) {
586
            $this->addPropertyMethods($property);
587
        }
588
    }
589
590
    /**
591
     * @param array<string, mixed> $property
592
     *
593
     * @return void
594
     */
595
    protected function addPropertyMethods(array $property): void
596
    {
597
        $this->buildGetterAndSetter($property);
598
599
        if ($this->isCollection($property) || $this->isArray($property)) {
600
            $this->buildAddMethod($property);
601
        }
602
603
        $this->buildRequireMethod($property);
604
        $this->buildStrictPropertyMethods($property);
605
    }
606
607
    /**
608
     * @param array<string, mixed> $property
609
     *
610
     * @return void
611
     */
612
    protected function buildStrictPropertyMethods(array $property): void
613
    {
614
        if (!$this->isStrictProperty($property)) {
615
            return;
616
        }
617
618
        if ($this->isAssociativeArray($property)) {
619
            $this->buildGetCollectionElementMethod($property);
620
        }
621
    }
622
623
    /**
624
     * @return array
625
     */
626
    public function getConstructorDefinition(): array
627
    {
628
        return $this->constructorDefinition;
629
    }
630
631
    /**
632
     * @return array
633
     */
634
    public function getMethods(): array
635
    {
636
        return $this->methods;
637
    }
638
639
    /**
640
     * @return array
641
     */
642
    public function getNormalizedProperties(): array
643
    {
644
        return $this->normalizedProperties;
645
    }
646
647
    /**
648
     * @return string|null
649
     */
650
    public function getDeprecationDescription(): ?string
651
    {
652
        return $this->deprecationDescription;
653
    }
654
655
    /**
656
     * @param array<string, mixed> $property
657
     *
658
     * @return void
659
     */
660
    protected function buildGetterAndSetter(array $property): void
661
    {
662
        $this->buildSetMethod($property);
663
        $this->buildGetMethod($property);
664
665
        if ($this->isPropertyCollection($property)) {
666
            $this->buildSetOrFailMethod($property);
667
            $this->buildGetOrFailMethod($property);
668
        }
669
    }
670
671
    /**
672
     * @param array<string, mixed> $property
673
     *
674
     * @return void
675
     */
676
    protected function buildSetOrFailMethod(array $property): void
677
    {
678
        $propertyName = $this->getPropertyName($property);
679
        $defaultSetMethodName = 'set' . ucfirst($propertyName);
680
        $methodName = sprintf('%sOrFail', $defaultSetMethodName);
681
682
        $method = [
683
            'name' => $methodName,
684
            'defaultSetMethodName' => $defaultSetMethodName,
685
            'property' => $propertyName,
686
            'propertyConst' => $this->getPropertyConstantName($property),
687
            'var' => $this->buildSetArgumentType($property),
688
            'bundles' => $property['bundles'],
689
            'typeHint' => null,
690
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
691
        ];
692
        $method = $this->addSetOrFailTypeHint($method, $property);
693
694
        if ($this->propertyHasTypeShim($property)) {
695
            $method['typeShimNotice'] = $this->buildTypeShimNotice(
696
                $property['type'],
697
                $this->getPropertyTypeShim($property),
698
            );
699
        }
700
701
        $this->methods[$methodName] = $method;
702
    }
703
704
    /**
705
     * @param array<string, mixed> $property
706
     *
707
     * @return void
708
     */
709
    protected function buildGetOrFailMethod(array $property): void
710
    {
711
        $propertyName = $this->getPropertyName($property);
712
        $methodName = sprintf('get%sOrFail', ucfirst($propertyName));
713
        $method = [
714
            'name' => $methodName,
715
            'property' => $propertyName,
716
            'propertyConst' => $this->getPropertyConstantName($property),
717
            'return' => preg_replace('/\|null$/', '', $this->getReturnType($property)),
718
            'bundles' => $property['bundles'],
719
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
720
            'isAbstractAttributesTransfer' => $this->isTypeAbstractAttributesTransfer($property),
721
        ];
722
723
        $method = $this->addGetOrFailTypeHint($method, $property);
724
        $this->methods[$methodName] = $method;
725
    }
726
727
    /**
728
     * @param array<string, mixed> $property
729
     *
730
     * @return string
731
     */
732
    protected function getPropertyConstantName(array $property): string
733
    {
734
        $filter = new CamelCaseToUnderscore();
735
        /** @var string $value */
736
        $value = $filter->filter($property['name']);
737
738
        return mb_strtoupper($value);
739
    }
740
741
    /**
742
     * @param array<string, mixed> $property
743
     *
744
     * @return string
745
     */
746
    protected function getPropertyName(array $property): string
747
    {
748
        $filter = new UnderscoreToCamelCase();
749
        /** @var string $value */
750
        $value = $filter->filter($property['name']);
751
752
        return lcfirst($value);
753
    }
754
755
    /**
756
     * @param array<string, mixed> $property
757
     *
758
     * @return string
759
     */
760
    protected function getReturnType(array $property): string
761
    {
762
        if ($this->isValueObject($property)) {
763
            return sprintf('\%s|null', $this->getValueObjectFullyQualifiedClassName($property));
764
        }
765
766
        if ($this->isTypedArray($property)) {
767
            $type = preg_replace('/\[\]/', '', $property['type']);
768
769
            return $type . '[]';
770
        }
771
772
        if ($this->isPrimitiveArray($property)) {
773
            return 'array|null';
774
        }
775
776
        if ($this->isArrayCollection($property)) {
777
            return 'array';
778
        }
779
780
        if ($this->isTypeAbstractAttributesTransfer($property)) {
781
            return '\\' . AbstractTransfer::class . '|null';
782
        }
783
784
        if ($this->isCollection($property)) {
785
            return $this->buildCollectionDocTypeHint($property);
786
        }
787
788
        if ($this->isTypeTransferObject($property)) {
789
            return '\Generated\Shared\Transfer\\' . $property['type'] . '|null';
790
        }
791
792
        return $property['type'] . '|null';
793
    }
794
795
    /**
796
     * @param array<string, mixed> $property
797
     *
798
     * @return bool
799
     */
800
    protected function isCollection(array $property): bool
801
    {
802
        return (bool)preg_match('/((.*?)\[\])/', $property['type']);
803
    }
804
805
    /**
806
     * @param array<string, mixed> $property
807
     *
808
     * @return bool
809
     */
810
    protected function isPropertyCollection(array $property): bool
811
    {
812
        return !$this->isArrayCollection($property) && !$this->isCollection($property);
813
    }
814
815
    /**
816
     * @param array<string, mixed> $property
817
     *
818
     * @return bool
819
     */
820
    protected function isArray(array $property): bool
821
    {
822
        return ($property['type'] === 'array' || $property['type'] === '[]' || $this->isTypedArray($property));
823
    }
824
825
    /**
826
     * @param array<string, mixed> $property
827
     *
828
     * @return bool
829
     */
830
    protected function isPrimitiveArray(array $property): bool
831
    {
832
        if (!$this->isStrictProperty($property)) {
833
            return false;
834
        }
835
836
        return $property['type'] === 'array' && !isset($property['singular']) && !$this->isAssociativeArray($property);
837
    }
838
839
    /**
840
     * @param array<string, mixed> $property
841
     *
842
     * @return bool
843
     */
844
    protected function isArrayCollection(array $property): bool
845
    {
846
        if ($this->isStrictProperty($property)) {
847
            return $this->isArray($property) && !$this->isPrimitiveArray($property);
848
        }
849
850
        return $this->isArray($property);
851
    }
852
853
    /**
854
     * @param array<string, mixed> $property
855
     *
856
     * @return bool
857
     */
858
    protected function isAssociativeArray(array $property): bool
859
    {
860
        return isset($property['associative']) && filter_var($property['associative'], FILTER_VALIDATE_BOOLEAN);
861
    }
862
863
    /**
864
     * @param array<string, mixed> $property
865
     *
866
     * @return bool
867
     */
868
    protected function isTypedArray(array $property): bool
869
    {
870
        return (bool)preg_match('/array\[\]|callable\[\]|int\[\]|integer\[\]|float\[\]|decimal\[\]|string\[\]|bool\[\]|boolean\[\]|iterable\[\]|object\[\]|resource\[\]|mixed\[\]/', $property['type']);
871
    }
872
873
    /**
874
     * @param array<string, mixed> $property
875
     *
876
     * @return string|bool
877
     */
878
    protected function getSetTypeHint(array $property)
879
    {
880
        if ($this->isStrictProperty($property)) {
881
            return $this->getStrictSetTypeHint($property);
882
        }
883
884
        if ($this->isArray($property) && isset($property['associative'])) {
885
            return false;
886
        }
887
888
        if ($this->isArray($property)) {
889
            return 'array';
890
        }
891
892
        if ($this->isTypeAbstractAttributesTransfer($property)) {
893
            return 'AbstractTransfer';
894
        }
895
896
        if ($this->isValueObject($property)) {
897
            $this->addUseStatement($this->getValueObjectFullyQualifiedClassName($property));
898
899
            return false;
900
        }
901
902
        if (preg_match('/^(string|int|integer|float|bool|boolean)$/', $property['type'])) {
903
            return false;
904
        }
905
906
        if ($this->isCollection($property)) {
907
            $this->addUseStatement(ArrayObject::class);
908
909
            return 'ArrayObject';
910
        }
911
912
        return $property['type'];
913
    }
914
915
    /**
916
     * @param array<string, mixed> $property
917
     *
918
     * @return string|bool
919
     */
920
    protected function getAddTypeHint(array $property)
921
    {
922
        if ($this->isStrictProperty($property)) {
923
            return $this->getStrictCollectionElementTypeHint($property);
924
        }
925
926
        if (preg_match('/^(string|int|integer|float|bool|boolean|mixed|resource|callable|iterable|array|\[\])/', $property['type'])) {
927
            return false;
928
        }
929
930
        return str_replace('[]', '', $property['type']);
931
    }
932
933
    /**
934
     * @param array<string, mixed> $property
935
     *
936
     * @return string|bool
937
     */
938
    protected function getStrictCollectionElementTypeHint(array $property)
939
    {
940
        if ($property['type'] === 'array' || $property['type'] === 'mixed') {
941
            return false;
942
        }
943
944
        return str_replace('[]', '', $property['type']);
945
    }
946
947
    /**
948
     * @param array<string, mixed> $property
949
     *
950
     * @return void
951
     */
952
    protected function buildGetMethod(array $property): void
953
    {
954
        $propertyName = $this->getPropertyName($property);
955
        $methodName = 'get' . ucfirst($propertyName);
956
        $method = [
957
            'name' => $methodName,
958
            'property' => $propertyName,
959
            'propertyConst' => $this->getPropertyConstantName($property),
960
            'return' => $this->buildGetReturnTypeData($property),
961
            'bundles' => $property['bundles'],
962
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
963
            'isAbstractAttributesTransfer' => $this->isTypeAbstractAttributesTransfer($property),
964
        ];
965
966
        if ($this->propertyHasTypeShim($property)) {
967
            $method['typeShimNotice'] = $this->buildTypeShimNotice(
968
                $property['type'],
969
                $this->getPropertyTypeShim($property),
970
            );
971
        }
972
973
        $method = $this->addGetReturnTypeHint($method, $property);
974
975
        $this->methods[$methodName] = $method;
976
    }
977
978
    /**
979
     * @param array<string, mixed> $property
980
     *
981
     * @return void
982
     */
983
    protected function buildSetMethod(array $property): void
984
    {
985
        $propertyName = $this->getPropertyName($property);
986
        $methodName = 'set' . ucfirst($propertyName);
987
        $method = [
988
            'name' => $methodName,
989
            'property' => $propertyName,
990
            'propertyConst' => $this->getPropertyConstantName($property),
991
            'var' => $this->buildSetArgumentType($property),
992
            'valueObject' => false,
993
            'bundles' => $property['bundles'],
994
            'typeHint' => null,
995
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
996
            'isAbstractAttributesTransfer' => $this->isTypeAbstractAttributesTransfer($property),
997
        ];
998
        $method = $this->addSetTypeHint($method, $property);
999
        $method = $this->addDefaultNull($method, $property);
1000
        $method = $this->setTypeAssertionMode($method);
1001
1002
        if ($this->isArrayCollection($property)) {
1003
            $method['setsArrayCollection'] = true;
1004
        }
1005
1006
        if ($this->isTypeAbstractAttributesTransfer($property)) {
1007
            $method['attributesTransfer'] = true;
1008
            $method['abstractAttributesTransfer'] = 'AbstractAttributesTransfer';
1009
        }
1010
1011
        if ($this->isCollectionPropertyTypeCheckNeeded($property)) {
1012
            $method['isCollectionPropertyTypeCheckNeeded'] = true;
1013
            $method['addMethodName'] = 'add' . ucfirst($this->getPropertySingularName($property));
1014
        }
1015
1016
        if ($this->propertyHasTypeShim($property)) {
1017
            $method['typeShimNotice'] = $this->buildTypeShimNotice(
1018
                $property['type'],
1019
                $this->getPropertyTypeShim($property),
1020
            );
1021
        }
1022
1023
        if ($this->isValueObject($property)) {
1024
            $method['valueObject'] = $this->getShortClassName(
1025
                $this->getValueObjectFullyQualifiedClassName($property),
1026
            );
1027
        }
1028
1029
        $this->methods[$methodName] = $method;
1030
    }
1031
1032
    /**
1033
     * @param array<string, mixed> $property
1034
     *
1035
     * @return void
1036
     */
1037
    protected function buildAddMethod(array $property): void
1038
    {
1039
        $parent = $this->getPropertyName($property);
1040
        $propertyConstant = $this->getPropertyConstantName($property);
1041
        $propertyName = $this->getPropertySingularName($property);
1042
        $methodName = 'add' . ucfirst($propertyName);
1043
1044
        $method = [
1045
            'name' => $methodName,
1046
            'property' => $propertyName,
1047
            'propertyConst' => $propertyConstant,
1048
            'parent' => $parent,
1049
            'var' => $this->buildAddArgumentType($property),
1050
            'bundles' => $property['bundles'],
1051
            'deprecationDescription' => $this->getPropertyDeprecationDescription($property),
1052
            'is_associative' => $this->isAssociativeArray($property),
1053
        ];
1054
1055
        if ($this->propertyHasTypeShim($property)) {
1056
            $method['typeShimNotice'] = $this->buildAddTypeShimNotice(
1057
                $property['type'],
1058
                $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

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