Passed
Push — master ( 966fd5...e75d59 )
by Pavel
03:58
created

EntityHydrator::hydarate()   B

Complexity

Conditions 6
Paths 14

Size

Total Lines 45
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 6.3188

Importance

Changes 0
Metric Value
dl 0
loc 45
ccs 23
cts 29
cp 0.7931
rs 8.439
c 0
b 0
f 0
cc 6
eloc 27
nc 14
nop 2
crap 6.3188
1
<?php
2
3
namespace Bankiru\Api\Doctrine\Hydration;
4
5
use Bankiru\Api\Doctrine\EntityManager;
6
use Bankiru\Api\Doctrine\Exception\HydrationException;
7
use Bankiru\Api\Doctrine\Exception\MappingException;
8
use Bankiru\Api\Doctrine\Mapping\ApiMetadata;
9
use Bankiru\Api\Doctrine\Mapping\EntityMetadata;
10
use Doctrine\Common\Collections\ArrayCollection;
11
use Doctrine\Common\Proxy\Proxy;
12
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
13
use Symfony\Component\PropertyAccess\PropertyAccessor;
14
15
final class EntityHydrator implements Hydrator
16
{
17
    /** @var  EntityMetadata */
18
    private $metadata;
19
    /** @var  EntityManager */
20
    private $manager;
21
22
    /**
23
     * EntityHydrator constructor.
24
     *
25
     * @param EntityManager $manager
26
     * @param ApiMetadata   $metadata
27
     */
28 18
    public function __construct(EntityManager $manager, ApiMetadata $metadata)
29
    {
30 18
        $this->manager  = $manager;
31 18
        $this->metadata = $metadata;
0 ignored issues
show
Documentation Bug introduced by
$metadata is of type object<Bankiru\Api\Doctrine\Mapping\ApiMetadata>, but the property $metadata was declared to be of type object<Bankiru\Api\Doctr...Mapping\EntityMetadata>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
32 18
    }
33
34
    /** {@inheritdoc} */
35 18
    public function hydarate($source, $entity = null)
36
    {
37 18
        if (null === $entity) {
38 18
            $entity = $this->metadata->getReflectionClass()->newInstance();
39 18
        }
40
41 18
        $acessor = new PropertyAccessor();
42 18
        foreach ($this->metadata->getFieldNames() as $fieldName) {
43 18
            $property = $this->metadata->getReflectionProperty($fieldName);
44
45 18
            $apiField = $this->metadata->getApiFieldName($fieldName);
46
47
            try {
48 18
                $value = $acessor->getValue($source, $apiField);
49 18
            } catch (NoSuchPropertyException $exception) {
50 4
                if (!$this->metadata->getFieldMapping($fieldName)['nullable']) {
51
                    throw new HydrationException(
52
                        sprintf(
53
                            'Field %s for property %s does not present in dehydrated data',
54
                            $apiField,
55
                            $fieldName
56
                        )
57
                    );
58
                }
59
60 4
                $property->setValue($entity, null);
61
62 4
                continue;
63
            }
64
65
            $type  =
66 18
                $this->manager->getConfiguration()->getTypeRegistry()->get($this->metadata->getTypeOfField($fieldName));
67 18
            $value = $type->fromApiValue($value, $this->metadata->getFieldOptions($fieldName));
68
69 18
            $property->setValue($entity, $value);
70 18
        }
71
72 18
        foreach ($this->metadata->getAssociationNames() as $fieldName) {
73 12
            $value    = $this->hydrateAssociation($fieldName, $entity, $source);
74 12
            $property = $this->metadata->getReflectionProperty($fieldName);
75 12
            $property->setValue($entity, $value);
76 18
        }
77
78 18
        return $entity;
79
    }
80
81
    /**
82
     * @param string    $field
83
     * @param \StdClass $source
84
     * @param object    $entity
85
     *
86
     * @return array|Proxy|object
87
     * @throws HydrationException
88
     * @throws MappingException
89
     */
90 12
    private function hydrateAssociation($field, $entity, $source)
91
    {
92 12
        $accessor        = new PropertyAccessor();
93 12
        $targetClassName = $this->metadata->getAssociationTargetClass($field);
94 12
        $mapping         = $this->metadata->getAssociationMapping($field);
95 12
        $mapping         = $mapping;
0 ignored issues
show
Bug introduced by
Why assign $mapping to itself?

This checks looks for cases where a variable has been assigned to itself.

This assignement can be removed without consequences.

Loading history...
96 12
        $targetPersister = $this->manager->getUnitOfWork()->getEntityPersister($targetClassName);
97 12
        $targetMetadata  = $this->manager->getClassMetadata($mapping['target']);
98 12
        $apiField        = $mapping['api_field'];
99 12
        $field           = $mapping['field'];
100 12
        $oid             = spl_object_hash($entity);
101
102 12
        if ($this->metadata->isSingleValuedAssociation($field)) {
103 11
            $identifiers = $this->metadata->getIdentifierValues($entity);
104 11
            if ($mapping['isOwningSide']) {
105
                try {
106 11
                    $value = $accessor->getValue($source, $apiField);
107 11
                } catch (NoSuchPropertyException $exception) {
108 8
                    if ($mapping['nullable']) {
109 8
                        $this->manager->getUnitOfWork()->setOriginalEntityProperty($oid, $field, null);
110
111 8
                        return null;
112
                    }
113
114
                    throw new HydrationException(
115
                        sprintf('Api field %s for property %s does not present in response', $apiField, $field)
116
                    );
117
                }
118
119 6
                if (null === $value) {
120 1
                    return null;
121
                }
122
123 5
                if ($targetMetadata->isIdentifierComposite()) {
124
                    throw new HydrationException('Composite references not supported');
125
                }
126
127 5
                $targetIdsNames = $targetMetadata->getIdentifierFieldNames();
128 5
                $targetIdName   = array_shift($targetIdsNames);
129 5
                $type           = $this->manager
130 5
                    ->getConfiguration()
131 5
                    ->getTypeRegistry()
132 5
                    ->get($targetMetadata->getTypeOfField($targetIdName));
133
134
                $identifiers =
135 5
                    [$targetIdName => $type->fromApiValue($value, $this->metadata->getFieldOptions($targetIdName))];
136 5
            }
137
138 5
            $newValue = $targetPersister->getToOneEntity($mapping, $entity, $identifiers);
139
140 5
            $this->manager->getUnitOfWork()->setOriginalEntityProperty($oid, $field, $newValue);
141
142 5
            return $newValue;
143
        }
144
145 11
        if ($this->metadata->isCollectionValuedAssociation($field)) {
146 11
            if ($mapping['type'] & EntityMetadata::ONE_TO_MANY) {
147 10
                $newValue = $targetPersister->getOneToManyCollection($mapping, $entity);
148
149 10
                $this->manager->getUnitOfWork()->setOriginalEntityProperty($oid, $field, $newValue);
150
151 10
                return $newValue;
152
            } else {
153
                try {
154 1
                    $value = $accessor->getValue($source, $apiField);
155 1
                } catch (NoSuchPropertyException $exception) {
156 1
                    if ($mapping['nullable']) {
157 1
                        $this->manager->getUnitOfWork()->setOriginalEntityProperty($oid, $field, new ArrayCollection());
158
159 1
                        return new ArrayCollection();
160
                    }
161
162
                    throw new HydrationException(
163
                        sprintf('Api field %s for property %s does not present in response', $apiField, $field)
164
                    );
165
                }
166
167 1
                if (null === $value) {
168
                    return new ArrayCollection();
169
                }
170
171 1
                if (!is_array($value)) {
172
                    throw new \InvalidArgumentException(
173
                        'ManyToMany collection value should be an array of identifiers'
174
                    );
175
                }
176
177 1
                $collection = $this->manager
178 1
                    ->getUnitOfWork()
179 1
                    ->getCollectionPersister($mapping)
180 1
                    ->getManyToManyCollection($value);
181
182 1
                $this->manager->getUnitOfWork()->setOriginalEntityProperty($oid, $field, $collection);
183
184 1
                return $collection;
185
            }
186
        }
187
188
        throw new MappingException('Invalid metadata association type');
189
    }
190
}
191