HydratorGenerator   F
last analyzed

Complexity

Total Complexity 74

Size/Duplication

Total Lines 806
Duplicated Lines 0 %

Test Coverage

Coverage 99.01%

Importance

Changes 0
Metric Value
wmc 74
eloc 291
dl 0
loc 806
ccs 300
cts 303
cp 0.9901
rs 2.48
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A hydratorFullClassName() 0 3 1
A generate() 0 5 1
A __construct() 0 10 1
A hydratorClassName() 0 3 1
A makeAccessor() 0 13 3
A hydratorNamespace() 0 3 1
B resolveHydrators() 0 16 7
A hydratorTemplate() 0 17 1
A generateEmbeddedHydrator() 0 7 2
A normalizeClassName() 0 3 1
A generateExtractValue() 0 26 5
A generateHydrateOneCaseEmbedded() 0 9 2
A generateEmbeddedClasses() 0 3 1
A generateHydrateOneCaseAttribute() 0 17 2
A generateAttributeFlatHydrate() 0 38 5
A generateExtractOneCaseEmbedded() 0 11 2
A generateHydrateBody() 0 15 2
A generateFlatHydrate() 0 27 4
A generateHydrateOneBody() 0 17 3
A generateExtractOneCaseAttribute() 0 19 3
A generateFlatExtract() 0 7 1
A generateExtractBody() 0 7 1
A generateExtractOneBody() 0 17 3
A generateFlatExtractAll() 0 43 5
A generateExtractSelected() 0 30 3
A generateFlatExtractSelected() 0 28 3
A generateExtractAll() 0 20 4
A generateAttributeHydrate() 0 23 2
A generateEmbeddedHydrate() 0 23 4

How to fix   Complexity   

Complex Class

Complex classes like HydratorGenerator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use HydratorGenerator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Bdf\Prime\Entity\Hydrator;
4
5
use Bdf\Prime\Entity\Hydrator\Exception\HydratorGenerationException;
6
use Bdf\Prime\Entity\Hydrator\Generator\AccessorResolver;
7
use Bdf\Prime\Entity\Hydrator\Generator\AttributeInfo;
8
use Bdf\Prime\Entity\Hydrator\Generator\AttributesResolver;
9
use Bdf\Prime\Entity\Hydrator\Generator\ClassAccessor;
10
use Bdf\Prime\Entity\Hydrator\Generator\CodeGenerator;
11
use Bdf\Prime\Entity\Hydrator\Generator\EmbeddedInfo;
12
use Bdf\Prime\Entity\Hydrator\Generator\TypeAccessor;
13
use Bdf\Prime\Mapper\Mapper;
14
use Bdf\Prime\Mapper\SingleTableInheritanceMapper;
15
use Bdf\Prime\ServiceLocator;
16
17
/**
18
 * Generator for hydrator classes
19
 */
20
class HydratorGenerator
21
{
22
    /**
23
     * The stub hydrator file name
24
     *
25
     * @var string
26
     */
27
    private $stub = __DIR__.'/Generator/stubs/hydrator.php.stub';
28
29
    /**
30
     * @var CodeGenerator
31
     */
32
    private $code;
33
34
    /**
35
     * @var ClassAccessor
36
     */
37
    private $accessor;
38
39
    /**
40
     * @var AccessorResolver
41
     */
42
    private $accessors;
43
44
    /**
45
     * @var AttributesResolver
46
     */
47
    private $resolver;
48
49
    /**
50
     * @var ServiceLocator
51
     */
52
    private $prime;
53
54
    /**
55
     * @var Mapper
56
     */
57
    private $mapper;
58
59
    /**
60
     * @var string
61
     */
62
    private $className;
63
64
    /**
65
     * @var string
66
     */
67
    private $interface = HydratorGeneratedInterface::class;
68
69
    /**
70
     * @var array
71
     */
72
    private $embeddedHydrators = [];
73
74
75
    /**
76
     * HydratorGenerator constructor.
77
     *
78
     * @param ServiceLocator $prime
79
     * @param Mapper $mapper
80
     * @param string $className
81
     *
82
     * @throws HydratorGenerationException
83
     */
84 227
    public function __construct(ServiceLocator $prime, Mapper $mapper, $className)
85
    {
86 227
        $this->prime = $prime;
87 227
        $this->mapper = $mapper;
88 227
        $this->className = $className;
89
90 227
        $this->code = new CodeGenerator();
91 227
        $this->accessor = $this->makeAccessor();
92 227
        $this->resolver = new AttributesResolver($mapper, $prime);
93 227
        $this->accessors = new AccessorResolver($this->accessor, $this->resolver, $this->code);
94
    }
95
96 227
    private function makeAccessor(): ClassAccessor
97
    {
98 227
        $subClass = [];
99
100
        // The mapper has inheritance, and it's not the inherited one
101
        if (
102 227
            $this->mapper instanceof SingleTableInheritanceMapper
103 227
            && !in_array(get_class($this->mapper), $this->mapper->getDiscriminatorMap())
104
        ) {
105 23
            $subClass = $this->mapper->getEntityMap();
106
        }
107
108 227
        return new ClassAccessor($this->className, ClassAccessor::SCOPE_INHERIT, $subClass);
109
    }
110
111
    /**
112
     * Get the hydrator namespace
113
     *
114
     * @return string
115
     */
116 227
    public function hydratorNamespace()
117
    {
118 227
        return implode('\\', array_slice(explode('\\', $this->className), 0, -1));
119
    }
120
121
    /**
122
     * Get the hydrator class name, without namespace
123
     *
124
     * @return string
125
     */
126 227
    public function hydratorClassName()
127
    {
128 227
        return 'Hydrator_' . str_replace('\\', '_', $this->className);
129
    }
130
131
    /**
132
     * Get the full class name (namespace + class name)
133
     *
134
     * @return string
135
     */
136 209
    public function hydratorFullClassName()
137
    {
138 209
        return $this->hydratorNamespace() . '\\' . $this->hydratorClassName();
139
    }
140
141
    /**
142
     * Generate the hydrator class code
143
     *
144
     * @return string
145
     */
146 33
    public function generate()
147
    {
148 33
        $this->resolveHydrators();
149
150 33
        return $this->hydratorTemplate();
151
    }
152
153
    /**
154
     * Resolve hydrator properties
155
     *
156
     * @return void
157
     */
158 33
    protected function resolveHydrators(): void
159
    {
160 33
        $classes = [];
161
162 33
        foreach ($this->resolver->rootEmbeddeds() as $embedded) {
163 26
            foreach ($embedded->classes() as $class) {
164
                // For now (1.6) hydrators are used only for hydrate / extract. Remove the isImportable if hydrator are use in mapping context.
165 26
                if ($class === $this->className || !$this->resolver->isEntity($class) || $this->resolver->isImportable($class)) {
166 25
                    continue;
167
                }
168
169 1
                if (!isset($classes[$class])) {
170 1
                    $classes[$class] = true;
171
172 1
                    $property = '__' . str_replace('\\', '_', $class) . '_hydrator';
173 1
                    $this->embeddedHydrators[$class] = $property;
174
                }
175
            }
176
        }
177
    }
178
179
    /**
180
     * Get the hydrator class template
181
     *
182
     * @return string
183
     *
184
     * @throws HydratorGenerationException
185
     */
186 33
    protected function hydratorTemplate()
187
    {
188 33
        return $this->code->generate($this->stub, [
189 33
            'namespace'                 => $this->code->namespace($this->hydratorNamespace()),
190 33
            'normalizedEntityClassName' => $this->normalizeClassName($this->className),
191 33
            'hydratorClassName'         => $this->hydratorClassName(),
192 33
            'hydratorInterface'         => $this->normalizeClassName($this->interface),
193 33
            'properties'                => $this->code->properties($this->embeddedHydrators),
194 33
            'constructor'               => $this->code->simpleConstructor($this->embeddedHydrators),
195 33
            'hydrateBody'               => $this->generateHydrateBody(),
196 33
            'extractBody'               => $this->generateExtractBody(),
197 33
            'flatExtractBody'           => $this->generateFlatExtract(),
198 33
            'flatHydrateBody'           => $this->generateFlatHydrate(),
199 33
            'extractOneBody'            => $this->generateExtractOneBody(),
200 33
            'hydrateOneBody'            => $this->generateHydrateOneBody(),
201 33
            'entityClassName'           => $this->className,
202 33
            'embeddedClasses'           => $this->generateEmbeddedClasses(),
203 33
        ]);
204
    }
205
206
    /**
207
     * Generate the hydrate() method body
208
     *
209
     * @return string
210
     *
211
     * @throws HydratorGenerationException
212
     */
213 33
    protected function generateHydrateBody()
214
    {
215 33
        $out = '';
216
217 33
        foreach ($this->resolver->rootAttributes() as $attribute) {
218 33
            $out .= <<<PHP
219 33
if (array_key_exists('{$attribute->name()}', \$data)) {
220 33
{$this->code->indent($this->generateAttributeHydrate($attribute), 1)}
221
}
222
223
224 33
PHP;
225
        }
226
227 31
        return $out;
228
    }
229
230
    /**
231
     * Generate the hydration code for one attribute
232
     *
233
     * @param AttributeInfo $attribute
234
     *
235
     * @return string
236
     *
237
     * @throws HydratorGenerationException
238
     */
239 33
    protected function generateAttributeHydrate(AttributeInfo $attribute)
240
    {
241 33
        $out = '';
242
243 33
        $value = '$data[\''.$attribute->name().'\']';
244
245 33
        if ($attribute->isEmbedded()) {
246 26
            $out .= <<<PHP
247 26
if (is_array({$value})) {
248 26
{$this->code->indent($this->generateEmbeddedHydrate($attribute), 1)}
249
} else {
250 26
    {$this->accessor->setter('$object', $attribute->property(), $value)};
251
}
252 26
PHP;
253
        } else {
254 31
            $out .= $this->accessor->setter('$object', $attribute->property(), $value).';';
255
        }
256
257 31
        return <<<PHP
258 31
try {
259 31
{$this->code->indent($out, 1)}
260
} catch (\TypeError \$e) {
261 31
    throw new \Bdf\Prime\Entity\Hydrator\Exception\InvalidTypeException(\$e, '{$attribute->type()}');
262
}
263 31
PHP;
264
    }
265
266
    /**
267
     * Generate embedded hydrator
268
     *
269
     * @param AttributeInfo $attribute
270
     *
271
     * @return string
272
     *
273
     * @throws HydratorGenerationException
274
     */
275 26
    protected function generateEmbeddedHydrate(AttributeInfo $attribute)
276
    {
277
        // We can have multiple entity classes for one attribute : morph
278 26
        $varName = '$__rel_' . str_replace('.', '_', $attribute->name());
279
280 26
        $hydrators = [];
281
282 26
        foreach ($attribute->embedded()->classes() as $class) {
283 26
            if ($this->resolver->isImportable($class)) {
284
                // For other objects (Collections) use import() method
285 25
                $hydrators[$this->normalizeClassName($class)] = "{$varName}->import(\$data['{$attribute->name()}']);";
286 1
            } elseif ($this->resolver->isEntity($class)) {
287
                // For Entities, use hydrators
288 1
                $hydrators[$this->normalizeClassName($class)] = "{$this->generateEmbeddedHydrator($class)}->hydrate({$varName}, \$data['{$attribute->name()}']);";
289
            } else {
290
                throw new HydratorGenerationException($class, 'Cannot generate embedded hydration for the property "'.$attribute->name().'"');
291
            }
292
        }
293
294 26
        return <<<PHP
295 26
{$varName} = {$this->accessor->getter('$object', $attribute->property())};
296
297 26
{$this->code->switchIntanceOf($varName, $hydrators)}
298 26
PHP;
299
    }
300
301
    /**
302
     * @param string $class
303
     *
304
     * @return string
305
     */
306 1
    protected function generateEmbeddedHydrator($class)
307
    {
308 1
        if ($class === $this->className) {
309
            return '$this';
310
        }
311
312 1
        return '$this->' . $this->embeddedHydrators[$class];
313
    }
314
315
    /**
316
     * Add the root namespace
317
     *
318
     * @param string $className
319
     *
320
     * @return string
321
     */
322 33
    protected function normalizeClassName($className)
323
    {
324 33
        return '\\' . ltrim($className, '\\');
325
    }
326
327
    /**
328
     * Generate the embedded entities classes list
329
     *
330
     * @return string
331
     */
332 31
    protected function generateEmbeddedClasses()
333
    {
334 31
        return $this->code->export(array_keys($this->embeddedHydrators));
335
    }
336
337
    /**
338
     * Generate the {@link HydratorInterface::extract()} method's body
339
     *
340
     * @return string
341
     *
342
     * @throws HydratorGenerationException
343
     */
344 31
    protected function generateExtractBody()
345
    {
346 31
        return <<<PHP
347 31
if (empty(\$attributes)) {
348 31
{$this->code->indent($this->generateExtractAll(), 1)}
349
} else {
350 31
{$this->code->indent($this->generateExtractSelected(), 1)}
351
}
352 31
PHP;
353
    }
354
355
    /**
356
     * Generate extract method's code for extract all attributes
357
     *
358
     * @return string
359
     *
360
     * @throws HydratorGenerationException
361
     */
362 31
    protected function generateExtractAll()
363
    {
364 31
        $lines = [];
365 31
        $possiblyNotInitialized = [];
366
367 31
        foreach ($this->resolver->rootAttributes() as $attribute) {
368 31
            if ($attribute->isInitializedByDefault()) {
369 30
                $lines[] = "'{$attribute->name()}' => ({$this->generateExtractValue($attribute)})";
370
            } else {
371 6
                $possiblyNotInitialized[] = "try { \$values['{$attribute->name()}'] = {$this->generateExtractValue($attribute)}; } catch (\Error \$e) { /** Ignore not initialized properties */ }";
372
            }
373
        }
374
375 31
        if (empty($possiblyNotInitialized)) {
376 26
            return 'return [' . implode(', ', $lines) . '];';
377
        }
378
379 6
        return '$values = [' . implode(', ', $lines) . '];' . PHP_EOL . PHP_EOL .
380 6
            implode(PHP_EOL, $possiblyNotInitialized) . PHP_EOL . PHP_EOL .
381 6
            'return $values;'
382 6
        ;
383
    }
384
385
    /**
386
     * Generate extract method's code for extract select attributes
387
     *
388
     * @return string
389
     *
390
     * @throws HydratorGenerationException
391
     */
392 31
    protected function generateExtractSelected()
393
    {
394 31
        $extracts = '';
395
396 31
        foreach ($this->resolver->rootAttributes() as $attribute) {
397 31
            $extractor = "\$values['{$attribute->name()}'] = {$this->generateExtractValue($attribute)};";
398
399 31
            if (!$attribute->isInitializedByDefault()) {
400 6
                $extractor = <<<PHP
401 6
try {
402 6
    {$extractor}
403
} catch (\Error \$e) {
404
    // Ignore not initialized properties
405
}
406 6
PHP;
407
            }
408
409 31
            $extracts .= <<<PHP
410 31
if (isset(\$attributes['{$attribute->name()}'])) {
411 31
{$this->code->indent($extractor, 1)}
412
}
413
414 31
PHP;
415
        }
416
417 31
        return <<<PHP
418 31
\$attributes = array_flip(\$attributes);
419
\$values = [];
420
421 31
{$extracts}
422
423
return \$values;
424 31
PHP;
425
    }
426
427
    /**
428
     * Generate the extraction code for one attribute
429
     *
430
     * @param AttributeInfo $attribute
431
     *
432
     * @return string
433
     *
434
     * @throws HydratorGenerationException
435
     */
436 31
    protected function generateExtractValue(AttributeInfo $attribute)
437
    {
438 31
        $line = '';
439
440 31
        if ($attribute->isEmbedded()) {
441 26
            $varName = '$__rel_' . str_replace('.', '_', $attribute->name());
442 26
            $line .= '(' . $varName . ' = '.$this->accessor->getter('$object', $attribute->property()) . ") === null ? null : ";
443
444 26
            foreach ($attribute->embedded()->classes() as $class) {
445 26
                if ($this->resolver->isImportable($class)) {
446 25
                    $line .= "({$varName} instanceof {$this->normalizeClassName($class)} ? {$varName}->export() : ";
447 1
                } elseif ($this->resolver->isEntity($class)) {
448 1
                    $line .= "({$varName} instanceof {$this->normalizeClassName($class)} ? {$this->generateEmbeddedHydrator($class)}->extract({$varName}) : ";
449
                } else {
450
                    throw new HydratorGenerationException($class, 'Cannot generate embedded hydration for the property "'.$attribute->name().'"');
451
                }
452
            }
453
454 26
            $line .= $varName.str_repeat(')', count($attribute->embedded()->classes()));
455
456 26
            return $line;
457
        }
458
459 29
        $line .= $this->accessor->getter('$object', $attribute->property());
460
461 29
        return $line;
462
    }
463
464
    /**
465
     * Generate the flatExtract (i.e. Mapper::prepareToRepository) method body
466
     *
467
     * @return string
468
     *
469
     * @throws HydratorGenerationException
470
     */
471 31
    protected function generateFlatExtract()
472
    {
473 31
        return <<<PHP
474 31
if (empty(\$attributes)) {
475 31
{$this->code->indent($this->generateFlatExtractAll(), 1)}
476
} else {
477 31
{$this->code->indent($this->generateFlatExtractSelected(), 1)}
478
}
479 31
PHP;
480
    }
481
482
    /**
483
     * Generate extract method's code for extract all attributes
484
     *
485
     * @return string
486
     *
487
     * @throws HydratorGenerationException
488
     */
489 31
    protected function generateFlatExtractAll()
490
    {
491 31
        $simpleArray = [];
492 31
        $extractors = [];
493
494 31
        foreach ($this->resolver->attributes() as $attribute) {
495 31
            if ($attribute->isEmbedded()) {
496 20
                $accessor = $this->accessors->embedded($attribute->embedded());
497 20
                $extractor = <<<PHP
498 20
{$accessor->getEmbedded('$__embedded')}
499 20
\$data['{$attribute->name()}'] = {$accessor->getter('$__embedded', $attribute->property())};
500 20
PHP;
501
502 20
                if (!$attribute->isInitializedByDefault()) {
503 2
                    $extractor = <<<PHP
504 2
try {
505 2
{$this->code->indent($extractor, 1)}
506
} catch (\Error \$e) {
507 2
    throw new \Bdf\Prime\Entity\Hydrator\Exception\UninitializedPropertyException('{$this->className}', '{$attribute->property()}');
508
}
509 2
PHP;
510
                }
511
512 20
                $extractors[] = $extractor;
513 29
            } elseif ($attribute->isInitializedByDefault()) {
514 28
                $simpleArray[] = "'{$attribute->name()}' => ({$this->accessor->getter('$object', $attribute->property())})";
515
            } else {
516 5
                $extractors[] = <<<PHP
517 5
try {
518 5
    \$data['{$attribute->name()}'] = {$this->accessor->getter('$object', $attribute->property())};
519
} catch (\Error \$e) {
520 5
    throw new \Bdf\Prime\Entity\Hydrator\Exception\UninitializedPropertyException('{$attribute->containerClassName()}', '{$attribute->property()}');
521
}
522 5
PHP;
523
            }
524
        }
525
526 31
        $simpleArray = implode(', ', $simpleArray);
527 31
        $extractors = implode(PHP_EOL, $extractors);
528
529 31
        return <<<PHP
530 31
\$data = [{$simpleArray}];
531 31
{$extractors}
532
533
return \$data;
534 31
PHP;
535
    }
536
537
    /**
538
     * Generate the extract method's code for extract selected attributes
539
     *
540
     * @return string
541
     *
542
     * @throws HydratorGenerationException
543
     */
544 31
    protected function generateFlatExtractSelected()
545
    {
546 31
        $lines = '';
547
548 31
        foreach ($this->resolver->attributes() as $attribute) {
549 31
            if ($attribute->isEmbedded()) {
550 20
                $accessor = $this->accessors->embedded($attribute->embedded());
551
552 20
                $code = <<<PHP
553 20
{$accessor->getEmbedded('$__embedded')}
554 20
\$data['{$attribute->name()}'] = {$accessor->getter('$__embedded', $attribute->property())};
555 20
PHP;
556
            } else {
557 29
                $code = "\$data['{$attribute->name()}'] = {$this->accessor->getter('$object', $attribute->property())};";
558
            }
559
560 31
            $lines .= <<<PHP
561 31
if (isset(\$attributes['{$attribute->name()}'])) {
562 31
{$this->code->indent($code, 1)}
563
}
564
565 31
PHP;
566
        }
567
568 31
        return <<<PHP
569 31
\$data = [];
570
571 31
{$lines}
572
573
return \$data;
574 31
PHP;
575
    }
576
577
    /**
578
     * Generate the flatHydrate method's body
579
     *
580
     * @return string
581
     *
582
     * @throws HydratorGenerationException
583
     */
584 31
    protected function generateFlatHydrate()
585
    {
586 31
        $types = new TypeAccessor($this->code);
587
588 31
        $out = '';
589 31
        $set = [];
590 31
        $relationKeys = [];
591
592 31
        foreach ($this->mapper->relations() as $property => $metadata) {
593 18
            $relationKeys[$metadata['localKey']] = true;
594
        }
595
596 31
        foreach ($this->resolver->attributes() as $attribute) {
597 31
            $set[$attribute->field()] = $this->generateAttributeFlatHydrate($attribute, $relationKeys, $types);
598
        }
599
600 31
        foreach ($set as $field => $declaration) {
601 31
            $out .= <<<PHP
602 31
if (array_key_exists('{$field}', \$data)) {
603 31
{$this->code->indent($declaration, 1)}
604
}
605
606
607 31
PHP;
608
        }
609
610 31
        return $types->generateDeclaration().$out;
611
    }
612
613
    /**
614
     * Generate flat hydration for one attribute
615
     *
616
     * @param AttributeInfo $attribute
617
     * @param array $relationKeys
618
     * @param TypeAccessor $types
619
     *
620
     * @return string
621
     *
622
     * @throws HydratorGenerationException
623
     */
624 31
    protected function generateAttributeFlatHydrate(AttributeInfo $attribute, array $relationKeys, TypeAccessor $types)
0 ignored issues
show
Unused Code introduced by
The parameter $relationKeys is not used and could be removed. ( Ignorable by Annotation )

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

624
    protected function generateAttributeFlatHydrate(AttributeInfo $attribute, /** @scrutinizer ignore-unused */ array $relationKeys, TypeAccessor $types)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
625
    {
626 31
        $options = '';
627 31
        if ($attribute->phpOptions()) {
628 6
            $options = "\$this->__metadata->fields['{$attribute->field()}']['phpOptions']";
629
        }
630
631 31
        $target = "\$value";
632 31
        $out = $target.' = '.$types->generateFromDatabase($attribute->type(), '$data[\''.$attribute->field().'\']', $options);
0 ignored issues
show
Bug introduced by
It seems like $attribute->type() can also be of type null; however, parameter $type of Bdf\Prime\Entity\Hydrato...:generateFromDatabase() 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

632
        $out = $target.' = '.$types->generateFromDatabase(/** @scrutinizer ignore-type */ $attribute->type(), '$data[\''.$attribute->field().'\']', $options);
Loading history...
633
634 31
        if (!$attribute->isEmbedded()) {
635 29
            if ($attribute->isNullable()) {
636 29
                return $out."\n".$this->accessor->setter('$object', $attribute->name(), $target, false).';';
637
            }
638
639 4
            return <<<PHP
640 4
{$out}
641
642 4
if ({$target} !== null) {
643 4
    {$this->accessor->setter('$object', $attribute->name(), $target, false)};
644
}
645 4
PHP;
646
        }
647
648 20
        $accessor = $this->accessors
649 20
            ->embedded($attribute->embedded())
650 20
            ->fullSetter($attribute->property(), $target, '$__embedded', '$data').';'
651 20
        ;
652
653 20
        if (!$attribute->isNullable()) {
654 2
            $accessor = <<<PHP
655 2
if ({$target} !== null) {
656 2
    {$accessor}
657
}
658 2
PHP;
659
        }
660
661 20
        return $this->code->lines([$out, $accessor]);
662
    }
663
664
    /**
665
     * Generate the extractOne() method's body
666
     *
667
     * @return string
668
     *
669
     * @throws HydratorGenerationException
670
     */
671 31
    protected function generateExtractOneBody()
672
    {
673 31
        $cases = [];
674
675 31
        foreach ($this->resolver->attributes() as $attribute) {
676 31
            $cases[$attribute->name()] = $this->generateExtractOneCaseAttribute($attribute);
677
        }
678
679 31
        foreach ($this->resolver->embeddeds() as $embedded) {
680 24
            $cases[$embedded->path()] = $this->generateExtractOneCaseEmbedded($embedded);
681
        }
682
683 31
        return $this->code->switch(
684 31
            '$attribute',
685 31
            $cases,
686 31
            <<<PHP
687 31
throw new \Bdf\Prime\Entity\Hydrator\Exception\FieldNotDeclaredException('{$this->className}', \$attribute);
688 31
PHP
689 31
        );
690
    }
691
692
    /**
693
     * Generate one case for extractOne switch, for attribute
694
     *
695
     * @param AttributeInfo $attribute
696
     *
697
     * @return string
698
     *
699
     * @throws HydratorGenerationException
700
     */
701 31
    protected function generateExtractOneCaseAttribute(AttributeInfo $attribute)
702
    {
703 31
        if ($attribute->isEmbedded()) {
704 20
            $accessor = $this->accessors->embedded($attribute->embedded());
705
706 20
            $body = $accessor->getEmbedded('$__embedded').$this->code->eol().'return '.$accessor->getter('$__embedded', $attribute->property()).';';
707
        } else {
708 29
            $body = "return {$this->accessor->getter('$object', $attribute->property())};";
709
        }
710
711 31
        if ($attribute->isInitializedByDefault()) {
712 30
            return $body;
713
        }
714
715 6
        return <<<PHP
716 6
try {
717 6
{$this->code->indent($body, 1)}
718
} catch (\Error \$e) {
719 6
    throw new \Bdf\Prime\Entity\Hydrator\Exception\UninitializedPropertyException('{$attribute->containerClassName()}', '{$attribute->property()}');
720
}
721 6
PHP;
722
    }
723
724
    /**
725
     * Generate one case for extractOne switch, for embedded
726
     *
727
     * @param EmbeddedInfo $embedded
728
     *
729
     * @return string
730
     *
731
     * @throws HydratorGenerationException
732
     */
733 24
    protected function generateExtractOneCaseEmbedded(EmbeddedInfo $embedded)
734
    {
735 24
        $varName = '$__' . str_replace('.', '_', $embedded->path());
736 24
        $code = $this->accessors->embedded($embedded)->getEmbedded($varName, false);
737 24
        $className = $embedded->isRoot() ? $this->mapper->getEntityClass() : $embedded->parent()->class();
738
739 24
        return <<<PHP
740 24
try {
741 24
{$this->code->indent($this->code->lines([$code, 'return ' . $varName . ';']), 1)}
742
} catch (\Error \$e) {
743 24
    throw new \Bdf\Prime\Entity\Hydrator\Exception\UninitializedPropertyException('{$className}', '{$embedded->property()}');
744
}
745 24
PHP;
746
    }
747
748
    /**
749
     * Generate hydrateOne() method's body
750
     *
751
     * @return string
752
     *
753
     * @throws HydratorGenerationException
754
     */
755 31
    protected function generateHydrateOneBody()
756
    {
757 31
        $cases = [];
758
759 31
        foreach ($this->resolver->attributes() as $attribute) {
760 31
            $cases[$attribute->name()] = $this->generateHydrateOneCaseAttribute($attribute);
761
        }
762
763 31
        foreach ($this->resolver->embeddeds() as $embedded) {
764 24
            $cases[$embedded->path()] = $this->generateHydrateOneCaseEmbedded($embedded);
765
        }
766
767 31
        return $this->code->switch(
768 31
            '$attribute',
769 31
            $cases,
770 31
            <<<PHP
771 31
throw new \Bdf\Prime\Entity\Hydrator\Exception\FieldNotDeclaredException('{$this->className}', \$attribute);
772 31
PHP
773 31
        );
774
    }
775
776
    /**
777
     * Generate one case for hydrateOne switch, for attribute
778
     *
779
     * @todo Exception on hydrate unresolved embedded attribute (like MapperHydrator)
780
     *
781
     * @param AttributeInfo $attribute
782
     *
783
     * @return string
784
     *
785
     * @throws HydratorGenerationException
786
     */
787 31
    protected function generateHydrateOneCaseAttribute($attribute)
788
    {
789 31
        if ($attribute->isEmbedded()) {
790 20
            $code = $this->accessors
791 20
                ->embedded($attribute->embedded())
792 20
                ->fullSetter($attribute->property(), '$value', '$__embedded').';'
793 20
            ;
794
        } else {
795 29
            $code = $this->accessor->setter('$object', $attribute->property(), '$value', false) . ';';
796
        }
797
798
        // Always surround with try catch because setter can also be typed
799 31
        return <<<PHP
800 31
try {
801 31
{$this->code->indent($code, 1)}
802
} catch (\TypeError \$e) {
803 31
    throw new \Bdf\Prime\Entity\Hydrator\Exception\InvalidTypeException(\$e, '{$attribute->type()}');
804
}
805 31
PHP;
806
    }
807
808
    /**
809
     * Generate one case for hydrateOne switch, for embedded
810
     *
811
     * @param EmbeddedInfo $embedded
812
     *
813
     * @return string
814
     *
815
     * @throws HydratorGenerationException
816
     */
817 24
    protected function generateHydrateOneCaseEmbedded(EmbeddedInfo $embedded)
818
    {
819 24
        if ($embedded->isRoot()) {
820 24
            return $this->accessor->setter('$object', $embedded->path(), '$value', false).';';
821
        }
822
823 5
        return $this->accessors
824 5
            ->embedded($embedded->parent())
825 5
            ->fullSetter($embedded->property(), '$value').';'
826 5
        ;
827
    }
828
}
829