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

ApiPersister::loadAll()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 16
c 0
b 0
f 0
ccs 12
cts 12
cp 1
rs 9.4285
cc 2
eloc 10
nc 2
nop 4
crap 2
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 17
    public function __construct(ApiEntityManager $manager, CrudsApiInterface $api)
34
    {
35 17
        $this->manager     = $manager;
36 17
        $this->metadata    = $api->getMetadata();
37 17
        $this->api         = $api;
38 17
        $this->transformer = new SearchArgumentsTransformer($this->metadata, $this->manager);
39 17
    }
40
41
    /** {@inheritdoc} */
42
    public function getClassMetadata()
43
    {
44
        return $this->metadata;
45
    }
46
47
    /** {@inheritdoc} */
48 4
    public function getCrudsApi()
49
    {
50 4
        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 2
    public function loadAll(array $criteria = [], array $orderBy = null, $limit = null, $offset = null)
80
    {
81 2
        $objects = $this->api->search(
82 2
            $this->transformer->transformCriteria($criteria),
83 2
            $this->transformer->transformOrder($orderBy),
84 2
            $limit,
85 1
            $offset
86 2
        );
87
88 2
        $entities = [];
89 2
        foreach ($objects as $object) {
90 2
            $entities[] = $this->manager->getUnitOfWork()->getOrCreateEntity($this->metadata->getName(), $object);
91 2
        }
92
93 2
        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 9
    public function getOneToManyCollection(array $assoc, $sourceEntity, $limit = null, $offset = null)
162
    {
163 9
        $targetClass = $assoc['target'];
164
        /** @var EntityMetadata $targetMetadata */
165 9
        $targetMetadata = $this->manager->getClassMetadata($targetClass);
166
167 9
        if ($this->metadata->isIdentifierComposite) {
168
            throw new \BadMethodCallException(__METHOD__ . ' on composite reference is not supported');
169
        }
170
171 9
        $apiCollection = new ApiCollection($this->manager, $targetMetadata);
172 9
        $apiCollection->setOwner($sourceEntity, $assoc);
173 9
        $apiCollection->setInitialized(false);
174
175 9
        return $apiCollection;
176
    }
177
178
    /** {@inheritdoc} */
179 4
    public function getToOneEntity(array $mapping, $sourceEntity, array $identifiers)
180
    {
181 4
        $metadata = $this->manager->getClassMetadata(get_class($sourceEntity));
182
183 4
        if (!$mapping['isOwningSide']) {
184
            $identifiers = $metadata->getIdentifierValues($sourceEntity);
185 1
        }
186
187 4
        return $this->manager->getReference($mapping['target'], $identifiers);
188
    }
189
190 4
    public function pushNewEntity($entity)
191
    {
192 4
        $this->pendingInserts[] = $entity;
193 4
    }
194
195 4
    public function flushNewEntities()
196
    {
197 4
        $result = [];
198 4
        foreach ($this->pendingInserts as $entity) {
199 4
            $result[] = [
200 4
                'generatedId' => $this->getCrudsApi()->create(
201 4
                    $this->transformer->transformCriteria($this->convertEntityToData($entity))
202 4
                ),
203 4
                'entity'      => $entity,
204
            ];
205 4
        }
206
207 4
        $this->pendingInserts = [];
208
209 4
        if ($this->metadata->isIdentifierNatural()) {
210
            return [];
211
        }
212
213 4
        return $result;
214
    }
215
216
    /**
217
     * Prepares the changeset of an entity for database insertion (UPDATE).
218
     *
219
     * The changeset is obtained from the currently running UnitOfWork.
220
     *
221
     * During this preparation the array that is passed as the second parameter is filled with
222
     * <columnName> => <value> pairs, grouped by table name.
223
     *
224
     * Example:
225
     * <code>
226
     * array(
227
     *    'foo_table' => array('column1' => 'value1', 'column2' => 'value2', ...),
228
     *    'bar_table' => array('columnX' => 'valueX', 'columnY' => 'valueY', ...),
229
     *    ...
230
     * )
231
     * </code>
232
     *
233
     * @param object $entity The entity for which to prepare the data.
234
     *
235
     * @return array The prepared data.
236
     */
237 1
    protected function prepareUpdateData($entity)
238
    {
239 1
        $result = [];
240 1
        $uow    = $this->manager->getUnitOfWork();
241 1
        foreach ($uow->getEntityChangeSet($entity) as $field => $change) {
242 1
            $newVal = $change[1];
243 1
            if (!$this->metadata->hasAssociation($field)) {
244
245
                $result[$this->metadata->getApiFieldName($field)] = $newVal;
246
                continue;
247
            }
248 1
            $assoc = $this->metadata->getAssociationMapping($field);
249
            // Only owning side of x-1 associations can have a FK column.
250 1
            if (!$assoc['isOwningSide'] || !($assoc['type'] & ApiMetadata::TO_ONE)) {
251
                continue;
252
            }
253 1
            if ($newVal !== null) {
254 1
                $oid = spl_object_hash($newVal);
255 1
                if (isset($this->pendingInserts[$oid]) || $uow->isScheduledForInsert($newVal)) {
256
                    // The associated entity $newVal is not yet persisted, so we must
257
                    // set $newVal = null, in order to insert a null value and schedule an
258
                    // extra update on the UnitOfWork.
259
                    $uow->scheduleExtraUpdate($entity, [$field => [null, $newVal]]);
260
                    $newVal = null;
261
                }
262 1
            }
263 1
            $newValId = null;
264 1
            if ($newVal !== null) {
265 1
                $newValId = $uow->getEntityIdentifier($newVal);
266 1
            }
267
            $targetClass                                      =
268 1
                $this->manager->getClassMetadata($assoc['target']);
269 1
            $result[$this->metadata->getApiFieldName($field)] = $newValId
270 1
                ? $newValId[$targetClass->getIdentifierFieldNames()[0]]
271 1
                : null;
272 1
        }
273
274 1
        return $result;
275
    }
276
277 4
    private function convertEntityToData($entity)
278
    {
279 4
        $entityData = [];
280 4
        foreach ($this->metadata->getReflectionProperties() as $name => $property) {
281 4
            if ($this->metadata->isIdentifier($name) && $this->metadata->isIdentifierRemote()) {
282 4
                continue;
283
            }
284 4
            if ($this->metadata->hasAssociation($name)) {
285 4
                $mapping = $this->metadata->getAssociationMapping($name);
286 4
                if (($mapping['type'] & ApiMetadata::TO_MANY) && !$mapping['isOwningSide']) {
287 2
                    continue;
288
                }
289 4
            }
290 4
            $entityData[$name] = $property->getValue($entity);
291 4
        }
292
293 4
        return $entityData;
294
    }
295
}
296