Completed
Pull Request — master (#14)
by Pavel
04:14
created

ApiPersister::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
c 0
b 0
f 0
ccs 6
cts 6
cp 1
rs 9.4285
cc 1
eloc 5
nc 1
nop 2
crap 1
1
<?php
2
3
namespace Bankiru\Api\Doctrine\Persister;
4
5
use Bankiru\Api\Doctrine\ApiEntityManager;
6
use Bankiru\Api\Doctrine\Mapping\ApiMetadata;
7
use Bankiru\Api\Doctrine\Mapping\EntityMetadata;
8
use Bankiru\Api\Doctrine\Proxy\ApiCollection;
9
use Bankiru\Api\Doctrine\Rpc\CrudsApiInterface;
10
use Bankiru\Api\Doctrine\Rpc\SearchArgumentsTransformer;
11
use Doctrine\Common\Collections\AbstractLazyCollection;
12
13
/** @internal */
14
class ApiPersister implements EntityPersister
15
{
16
    /** @var SearchArgumentsTransformer */
17
    private $transformer;
18
    /** @var  EntityMetadata */
19
    private $metadata;
20
    /** @var ApiEntityManager */
21
    private $manager;
22
    /** @var CrudsApiInterface */
23
    private $api;
24
    /** @var array */
25
    private $pendingInserts = [];
26
27
    /**
28
     * ApiPersister constructor.
29
     *
30
     * @param ApiEntityManager  $manager
31
     * @param CrudsApiInterface $api
32
     */
33 19
    public function __construct(ApiEntityManager $manager, CrudsApiInterface $api)
34
    {
35 19
        $this->manager     = $manager;
36 19
        $this->metadata    = $api->getMetadata();
37 19
        $this->api         = $api;
38 19
        $this->transformer = new SearchArgumentsTransformer($this->metadata, $this->manager);
39 19
    }
40
41
    /** {@inheritdoc} */
42
    public function getClassMetadata()
43
    {
44
        return $this->metadata;
45
    }
46
47
    /** {@inheritdoc} */
48 5
    public function getCrudsApi()
49
    {
50 5
        return $this->api;
51
    }
52
53
    /** {@inheritdoc} */
54 1
    public function update($entity)
55
    {
56 1
        $patch = $this->prepareUpdateData($entity);
57 1
        $data  = $this->convertEntityToData($entity);
58
59 1
        $this->api->patch(
60 1
            $this->transformer->transformCriteria($this->metadata->getIdentifierValues($entity)),
61 1
            $patch,
62
            $data
63 1
        );
64 1
    }
65
66
    /** {@inheritdoc} */
67 1
    public function delete($entity)
68
    {
69 1
        return $this->api->remove($this->transformer->transformCriteria($this->metadata->getIdentifierValues($entity)));
70
    }
71
72
    /** {@inheritdoc} */
73 1
    public function count($criteria = [])
74
    {
75 1
        return $this->api->count($this->transformer->transformCriteria($criteria));
76
    }
77
78
    /** {@inheritdoc} */
79 3
    public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null)
80
    {
81 3
        $objects = $this->api->search(
82 3
            $this->transformer->transformCriteria($criteria),
83 3
            $this->transformer->transformOrder($orderBy),
84 3
            $limit,
85
            $offset
86 3
        );
87
88 3
        $entities = [];
89 3
        foreach ($objects as $object) {
90 3
            $entities[] = $this->manager->getUnitOfWork()->getOrCreateEntity($this->metadata->getName(), $object);
91 3
        }
92
93 3
        return $entities;
94
    }
95
96
    /** {@inheritdoc} */
97
    public function loadOneToOneEntity(array $assoc, $sourceEntity, array $identifier = [])
98
    {
99
        if (false !== ($foundEntity = $this->manager->getUnitOfWork()->tryGetById($identifier, $assoc['target']))) {
100
            return $foundEntity;
101
        }
102
103
        // Get identifiers from entity if the entity is not the owning side
104
        if (!$assoc['isOwningSide']) {
105
            $identifier = $this->metadata->getIdentifierValues($sourceEntity);
106
        }
107
108
        return $this->loadById($identifier);
109
    }
110
111
    /** {@inheritdoc} */
112 11
    public function loadById(array $identifiers, $entity = null)
113
    {
114 11
        $body = $this->api->find($this->transformer->transformCriteria($identifiers));
115
116 11
        if (null === $body) {
117
            return null;
118
        }
119
120 11
        return $this->manager->getUnitOfWork()->getOrCreateEntity($this->metadata->getName(), $body);
121
    }
122
123
    /** {@inheritdoc} */
124
    public function refresh(array $id, $entity)
125
    {
126
        $this->loadById($id, $entity);
127
    }
128
129
    /** {@inheritdoc} */
130 2
    public function loadOneToManyCollection(array $assoc, $sourceEntity, AbstractLazyCollection $collection)
131
    {
132
        $criteria = [
133 2
            $assoc['mappedBy'] => $sourceEntity,
134 2
        ];
135
136 2
        $orderBy = isset($assoc['orderBy']) ? $assoc['orderBy'] : [];
137
138 2
        $source = $this->api->search(
139 2
            $this->transformer->transformCriteria($criteria),
140 2
            $this->transformer->transformOrder($orderBy)
141 2
        );
142
143 2
        $target = $this->manager->getClassMetadata($assoc['target']);
144
145 2
        foreach ($source as $object) {
146 2
            $entity = $this->manager->getUnitOfWork()->getOrCreateEntity($target->getName(), $object);
147 2
            if (isset($assoc['orderBy'])) {
148
                $index = $target->getReflectionProperty($assoc['orderBy'])->getValue($entity);
149
                $collection->set($index, $entity);
150
            } else {
151 2
                $collection->add($entity);
152
            }
153
154 2
            $target->getReflectionProperty($assoc['mappedBy'])->setValue($entity, $sourceEntity);
155 2
        }
156
157 2
        return $collection;
158
    }
159
160
    /** {@inheritdoc} */
161 10
    public function getOneToManyCollection(array $assoc, $sourceEntity, $limit = null, $offset = null)
162
    {
163 10
        $targetClass = $assoc['target'];
164
        /** @var EntityMetadata $targetMetadata */
165 10
        $targetMetadata = $this->manager->getClassMetadata($targetClass);
166
167 10
        if ($this->metadata->isIdentifierComposite) {
168
            throw new \BadMethodCallException(__METHOD__ . ' on composite reference is not supported');
169
        }
170
171 10
        $apiCollection = new ApiCollection($this->manager, $targetMetadata);
172 10
        $apiCollection->setOwner($sourceEntity, $assoc);
173 10
        $apiCollection->setInitialized(false);
174
175 10
        return $apiCollection;
176
    }
177
178
    /** {@inheritdoc} */
179 5
    public function getToOneEntity(array $mapping, $sourceEntity, array $identifiers)
180
    {
181 5
        $metadata = $this->manager->getClassMetadata(get_class($sourceEntity));
182
183 5
        if (!$mapping['isOwningSide']) {
184
            $identifiers = $metadata->getIdentifierValues($sourceEntity);
185 1
        }
186
187 5
        return $this->manager->getReference($mapping['target'], $identifiers);
188
    }
189
190 5
    public function pushNewEntity($entity)
191
    {
192 5
        $this->pendingInserts[] = $entity;
193 5
    }
194
195 5
    public function flushNewEntities()
196
    {
197 5
        $result = [];
198 5
        foreach ($this->pendingInserts as $entity) {
199 5
            $result[] = [
200 5
                'generatedId' => $this->getCrudsApi()->create($this->convertEntityToData($entity)),
201 5
                'entity'      => $entity,
202
            ];
203 5
        }
204
205 5
        $this->pendingInserts = [];
206
207 5
        if ($this->metadata->isIdentifierNatural()) {
208
            return [];
209
        }
210
211 5
        return $result;
212
    }
213
214
    /**
215
     * Prepares the changeset of an entity for database insertion (UPDATE).
216
     *
217
     * The changeset is obtained from the currently running UnitOfWork.
218
     *
219
     * During this preparation the array that is passed as the second parameter is filled with
220
     * <columnName> => <value> pairs, grouped by table name.
221
     *
222
     * Example:
223
     * <code>
224
     * array(
225
     *    'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...),
226
     *    'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...),
227
     *    ...
228
     * )
229
     * </code>
230
     *
231
     * @param object $entity The entity for which to prepare the data.
232
     *
233
     * @return array The prepared data.
234
     */
235 1
    protected function prepareUpdateData($entity)
236
    {
237 1
        $result = [];
238 1
        $uow    = $this->manager->getUnitOfWork();
239 1
        foreach ($uow->getEntityChangeSet($entity) as $field => $change) {
240 1
            $newVal = $change[1];
241 1
            if (!$this->metadata->hasAssociation($field)) {
242
243
                $result[$this->metadata->getApiFieldName($field)] = $newVal;
244
                continue;
245
            }
246 1
            $assoc = $this->metadata->getAssociationMapping($field);
247
            // Only owning side of x-1 associations can have a FK column.
248 1
            if (!$assoc['isOwningSide'] || !($assoc['type'] & ApiMetadata::TO_ONE)) {
249
                continue;
250
            }
251 1
            if ($newVal !== null) {
252 1
                $oid = spl_object_hash($newVal);
253 1
                if (isset($this->pendingInserts[$oid]) || $uow->isScheduledForInsert($newVal)) {
254
                    // The associated entity $newVal is not yet persisted, so we must
255
                    // set $newVal = null, in order to insert a null value and schedule an
256
                    // extra update on the UnitOfWork.
257
                    $uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]);
258
                    $newVal = null;
259
                }
260 1
            }
261 1
            $newValId = null;
262 1
            if ($newVal !== null) {
263 1
                $newValId = $uow->getEntityIdentifier($newVal);
264 1
            }
265
            $targetClass                                      =
266 1
                $this->manager->getClassMetadata($assoc['target']);
267 1
            $result[$this->metadata->getApiFieldName($field)] = $newValId
268 1
                ? $newValId[$targetClass->getIdentifierFieldNames()[0]]
269 1
                : null;
270 1
        }
271
272 1
        return $result;
273
    }
274
275 5
    private function convertEntityToData($entity)
276
    {
277 5
        $entityData = [];
278 5
        foreach ($this->metadata->getReflectionProperties() as $name => $property) {
279 5
            if ($this->metadata->isIdentifier($name) && $this->metadata->isIdentifierRemote()) {
280 5
                continue;
281
            }
282 5
            $apiField = $this->metadata->getApiFieldName($name);
283 5
            $value    = $property->getValue($entity);
284 5
            if (null === $value) {
285 5
                $entityData[$apiField] = $value;
286 5
                continue;
287
            }
288
289 2
            if ($this->metadata->hasAssociation($name)) {
290 2
                $mapping = $this->metadata->getAssociationMapping($name);
291 2
                if (($mapping['type'] & ApiMetadata::TO_MANY) && !$mapping['isOwningSide']) {
292 2
                    continue;
293
                }
294 2
                $target         = $this->metadata->getAssociationMapping($name)['target'];
295 2
                $targetMetadata = $this->manager->getClassMetadata($target);
296 2
                $value          = $targetMetadata->getIdentifierValues($value);
297 2
                $ids            = [];
298 2
                foreach ($value as $idName => $idValue) {
299 2
                    $typeName        = $targetMetadata->getTypeOfField($idName);
300 2
                    $idApiName       = $targetMetadata->getApiFieldName($idName);
301 2
                    $type            = $this->manager->getConfiguration()->getTypeRegistry()->get($typeName);
302 2
                    $idValue         = $type->toApiValue($idValue);
303 2
                    $ids[$idApiName] = $idValue;
304 2
                }
305 2
                if (!$targetMetadata->isIdentifierComposite()) {
0 ignored issues
show
Bug introduced by
The method isIdentifierComposite() does not exist on Doctrine\Common\Persistence\Mapping\ClassMetadata. Did you maybe mean isIdentifier()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
306 2
                    $ids = array_shift($ids);
307 2
                }
308 2
                $value = $ids;
309 2
            } else {
310
                $typeName = $this->metadata->getTypeOfField($name);
311
                $type     = $this->manager->getConfiguration()->getTypeRegistry()->get($typeName);
312
                $value    = $type->toApiValue($value);
313
            }
314
315 2
            $entityData[$apiField] = $value;
316 5
        }
317
318 5
        return $entityData;
319
    }
320
}
321