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

ApiPersister::loadOneToManyCollection()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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