Passed
Push — master ( 383b45...c02d9e )
by Pavel
01:44
created

CanHydrate::hydrateRelationships()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 27
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
eloc 13
nc 5
nop 3
dl 0
loc 27
ccs 14
cts 14
cp 1
crap 6
rs 8.439
c 0
b 0
f 0
1
<?php namespace Pz\Doctrine\Rest\Traits;
2
3
use Doctrine\Common\Collections\ArrayCollection;
4
use Doctrine\Common\Util\ClassUtils;
5
use Doctrine\ORM\Mapping\ClassMetadataInfo;
6
use Pz\Doctrine\Rest\Exceptions\RestException;
7
use Pz\Doctrine\Rest\RestRepository;
8
9
trait CanHydrate
10
{
11
    /**
12
     * @return RestRepository
13
     */
14
    abstract public function repository();
15
16
    /**
17
     * @param string|object $entity
18
     * @param array         $data
19
     * @param string        $scope
20
     *
21
     * @return object
22
     * @throws RestException
23
     */
24 6
    public function hydrateEntity($entity, array $data, $scope = '')
25
    {
26 6
        $hydrated = false;
27 6
        $entity = is_object($entity) ? $entity : new $entity;
28
29 6 View Code Duplication
        if (isset($data['attributes']) && is_array($data['attributes'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
30 6
            $entity = $this->hydrateAttributes($entity, $data['attributes'], $scope);
31 5
            $hydrated = true;
32
        }
33
34
35 6 View Code Duplication
        if (isset($data['relationships']) && is_array($data['relationships'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
36 3
            $entity = $this->hydrateRelationships($entity, $data['relationships'], $scope);
37 2
            $hydrated = true;
38
        }
39
40 6
        if (!$hydrated) {
41 1
            throw RestException::missingDataMembers($scope);
42
        }
43
44 5
        return $entity;
45
    }
46
47
    /**
48
     * @param        $entity
49
     * @param array  $attributes
50
     * @param string $scope
51
     *
52
     * @return mixed
53
     * @throws RestException
54
     */
55 6
    private function hydrateAttributes($entity, array $attributes, $scope = '')
56
    {
57 6
        $metadata = $this->repository()->getEntityManager()->getClassMetadata(ClassUtils::getClass($entity));
58
59 6
        foreach ($attributes as $name => $value) {
60 6
            if (!isset($metadata->reflFields[$name])) {
61 1
                throw RestException::unknownAttribute($scope.$name);
62
            }
63
64 5
            $this->setProperty($entity, $name, $value);
65
        }
66
67 5
        return $entity;
68
    }
69
70
    /**
71
     * @param       $entity
72
     * @param array $relationships
73
     * @param       $scope
74
     *
75
     * @return mixed
76
     * @throws RestException
77
     */
78 3
    private function hydrateRelationships($entity, array $relationships, $scope)
79
    {
80 3
        $metadata = $this->repository()->getEntityManager()->getClassMetadata(ClassUtils::getClass($entity));
81
82 3
        foreach ($relationships as $name => $data) {
83 3
            if (!isset($metadata->associationMappings[$name])) {
84 1
                throw RestException::unknownRelation($scope.$name);
85
            }
86
87 3
            $mapping = $metadata->associationMappings[$name];
88
89 3
            if (!isset($data['data'])) {
90 1
                throw RestException::missingData($scope.$name);
91
            }
92
93 3
            if (in_array($mapping['type'], [ClassMetadataInfo::ONE_TO_ONE, ClassMetadataInfo::MANY_TO_ONE])) {
94 3
                $this->setProperty($entity, $name,
95 3
                    $this->hydrateRelationData($mapping['targetEntity'], $data['data'], $scope.$name)
96
                );
97
            }
98
99 3
            if (in_array($mapping['type'], [ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::MANY_TO_MANY])) {
100 3
                $this->hydrateToManyRelation($entity, $name, $mapping['targetEntity'], $data['data'], $scope.$name);
101
            }
102
        }
103
104 2
        return $entity;
105
    }
106
107
    /**
108
     * Hydrate one relation.
109
     *
110
     * @param object $entity
111
     * @param string $name          Relation name
112
     * @param array  $targetEntity  Doctrine relation class name
113
     * @param mixed  $data
114
     * @param string $scope
115
     *
116
     * @return object
117
     * @throws RestException
118
     */
119 2
    private function hydrateToManyRelation($entity, $name, $targetEntity, $data, $scope)
120
    {
121 2
        if (!is_array($data)) {
122 1
            throw RestException::missingData($scope);
123
        }
124
125 1
        $this->setProperty($entity, $name,
126 1
            new ArrayCollection(array_map(
127 1
                function($item, $index) use ($targetEntity, $name, $scope) {
0 ignored issues
show
Unused Code introduced by
The import $name is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
128 1
                    return $this->hydrateRelationData($targetEntity, $item, $scope.'['.$index.']');
0 ignored issues
show
Bug introduced by
$targetEntity of type array is incompatible with the type string expected by parameter $class of Pz\Doctrine\Rest\Traits\...::hydrateRelationData(). ( Ignorable by Annotation )

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

128
                    return $this->hydrateRelationData(/** @scrutinizer ignore-type */ $targetEntity, $item, $scope.'['.$index.']');
Loading history...
129 1
                },
130 1
                $data,
131 1
                array_keys($data)
132
            ))
133
        );
134 1
    }
135
136
    /**
137
     * @param string $class
138
     * @param mixed  $data
139
     * @param string $scope
140
     *
141
     * @return object
142
     * @throws RestException
143
     * @throws \Doctrine\ORM\ORMException
144
     */
145 3
    private function hydrateRelationData($class, $data, $scope)
146
    {
147 3
        if (is_scalar($data)) {
148 1
            return $this->repository()->getEntityManager()->getReference($class, $data);
149
        }
150
151 3
        if (!is_array($data)) {
152 1
            throw RestException::missingData($scope);
153
        }
154
155 2
        if (isset($data['id']) && isset($data['type'])) {
156 2
            return $this->repository()->getEntityManager()->getReference($class, $data['id']);
157
        } else {
158 1
            return $this->hydrateEntity($class, $data, $scope.'.');
159
        }
160
    }
161
162
    /**
163
     * Set property on entity object.
164
     *
165
     * @param object $entity
166
     * @param string $name
167
     * @param mixed  $value
168
     *
169
     * @return object
170
     * @throws RestException
171
     */
172 6
    private function setProperty($entity, $name, $value)
173
    {
174 6
        $setter = 'set'.ucfirst($name);
175
176 6
        if (!method_exists($entity, $setter)) {
177 1
            $source = ['pointer' => $name, 'entity' => ClassUtils::getClass($entity), 'setter' => $setter];
178 1
            throw RestException::createUnprocessable('Setter not found for entity')
179 1
                ->error('missing-setter', $source, 'Missing field setter.');
180
        }
181
182 5
        $entity->$setter($value);
183
184 5
        return $entity;
185
    }
186
}
187