Completed
Push — master ( 99ca87...95904e )
by Andrey
23:44
created

ImportEngine::initEntity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 2
eloc 5
nc 2
nop 1
1
<?php
2
/**
3
 * @link    https://github.com/nnx-framework/doctrine-fixture-module
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace Nnx\JmsSerializerModule\DoctrineObjectEngine;
7
8
use Doctrine\Common\Persistence\ObjectManager;
9
use Interop\Container\ContainerInterface;
10
use Doctrine\Common\Util\Inflector;
11
use Doctrine\Common\Collections\Collection;
12
use DateTime;
13
use Nnx\JmsSerializerModule\DataContainer;
14
15
/**
16
 * Class ImportEngine
17
 *
18
 * @package Nnx\JmsSerializerModule\DoctrineObjectEngine
19
 */
20
class ImportEngine implements ImportEngineInterface
21
{
22
23
    /**
24
     * Контейнер с данными
25
     *
26
     * @var DataContainer\DataContainerInterface
27
     */
28
    private $dataContainer;
29
30
    /**
31
     * Метаданные необходимые для заполнения бд
32
     *
33
     * @var MetadataInterface
34
     */
35
    private $metadata;
36
37
    /**
38
     * Менеджер объектов доктрины
39
     *
40
     * @var ObjectManager
41
     */
42
    private $objectManager;
43
44
    /**
45
     * Связывает id контейнера с данными, с сущностью
46
     *
47
     * @var array
48
     */
49
    private $dataContainerIdToDoctrineEntity = [];
50
51
    /**
52
     * Компонет отвечающий за создание сущностей
53
     *
54
     * @var ContainerInterface
55
     */
56
    protected $entityLocator;
57
58
    /**
59
     * ImportEngine constructor.
60
     *
61
     * @param ContainerInterface $entityContainer
62
     */
63
    public function __construct(ContainerInterface $entityContainer)
64
    {
65
        $this->setEntityLocator($entityContainer);
66
    }
67
68
    /**
69
     * Запуск процесса импорта данных
70
     *
71
     * @param DataContainer\DataContainerInterface $dataContainer
72
     * @param MetadataInterface                    $metadata
73
     * @param ObjectManager                        $objectManager
74
     *
75
     *
76
     * @return void
77
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\InvalidSetValueException
78
     * @throws \Nnx\JmsSerializerModule\DataContainer\Exception\RuntimeException
79
     *
80
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\RuntimeException
81
     * @throws \UnexpectedValueException
82
     * @throws \Interop\Container\Exception\ContainerException
83
     * @throws \Interop\Container\Exception\NotFoundException
84
     */
85
    public function run(DataContainer\DataContainerInterface $dataContainer, MetadataInterface $metadata, ObjectManager $objectManager)
86
    {
87
        $this->dataContainer = $dataContainer;
88
        $this->metadata = $metadata;
89
        $this->objectManager = $objectManager;
90
91
        $this->initEntities();
92
        $this->hydrateAssociation();
93
        $this->hydrateProperties();
94
    }
95
96
    /**
97
     * Заполняет сущности данными
98
     *
99
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\RuntimeException
100
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\InvalidSetValueException
101
     */
102
    protected function hydrateProperties()
103
    {
104
        $index = $this->dataContainer->getIndex();
105
        $dataItems = $index->getEntities();
106
        foreach ($dataItems as $dataItem) {
107
            $properties = $dataItem->getProperties();
108
109
            $entity = $this->getDoctrineEntityByDataContainer($dataItem);
110
            $metadata = $this->objectManager->getClassMetadata(get_class($entity));
111
            foreach ($properties as $property) {
112
                $propertyName = $property->getName();
113
114
                $normalizePropertyName = Inflector::camelize($propertyName);
115
116
                $setter = 'set' . Inflector::classify($propertyName);
117
118
                $typeField = $metadata->getTypeOfField($normalizePropertyName);
119
120
                $value = $this->handleTypeConversions($property->getValue(), $typeField);
121
122
                try {
123
                    $metadata->getReflectionClass()->getMethod($setter)->invoke($entity, $value);
124
                } catch (\Exception $e) {
125
                    throw new Exception\InvalidSetValueException($e->getMessage(), $e->getCode(), $e);
126
                }
127
            }
128
        }
129
    }
130
131
    /**
132
     * Преобразование типов
133
     *
134
     * @param  mixed  $value
135
     * @param  string $typeOfField
136
     *
137
     * @return mixed
138
     */
139
    protected function handleTypeConversions($value, $typeOfField)
140
    {
141
        switch ($typeOfField) {
142
            case 'datetimetz':
143
            case 'datetime':
144
            case 'time':
145
            case 'date':
146
                if (is_int($value)) {
147
                    $dateTime = new DateTime();
148
                    $dateTime->setTimestamp($value);
149
                    $value = $dateTime;
150
                } elseif (is_string($value)) {
151
                    $value = '' === $value ? new DateTime() : new DateTime($value);
152
                }
153
                break;
154
            default:
155
        }
156
157
        return $value;
158
    }
159
160
161
    /**
162
     * Проставляет связи между сущностями
163
     *
164
     * @return void
165
     * @throws \Nnx\JmsSerializerModule\DataContainer\Exception\RuntimeException
166
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\RuntimeException
167
     */
168
    protected function hydrateAssociation()
169
    {
170
        $index = $this->dataContainer->getIndex();
171
        $dataItems = $index->getEntities();
172
        foreach ($dataItems as $dataItem) {
173
            if (!$this->metadata->hasAssociationsForEntity($dataItem)) {
174
                continue;
175
            }
176
177
            $associations = $this->metadata->getAssociationsForEntity($dataItem);
178
            $entity = $this->getDoctrineEntityByDataContainer($dataItem);
179
180
            $entityClassName = get_class($entity);
181
            $entityMetadata = $this->objectManager->getClassMetadata($entityClassName);
182
183
            $rClass = $entityMetadata->getReflectionClass();
184
            foreach ($associations as $associationName => $associationDataContainers) {
185
                if ($entityMetadata->isCollectionValuedAssociation($associationName)) {
186
                    $getter = 'get' . Inflector::classify($associationName);
187
                    $collection = $rClass->getMethod($getter)->invoke($entity);
188
189
                    if (!$collection instanceof Collection) {
190
                        $errMsg = sprintf('Property %s in entity %s not collection', $associationName, $entityClassName);
191
                        throw new Exception\RuntimeException($errMsg);
192
                    }
193
194
                    foreach ($associationDataContainers as $associationDataContainerId) {
195
                        $associationDataContainer = $index->getEntityById($associationDataContainerId);
196
                        $associationEntity = $this->getDoctrineEntityByDataContainer($associationDataContainer);
197
                        $collection->add($associationEntity);
198
                    }
199
                } elseif ($entityMetadata->isSingleValuedAssociation($associationName)) {
200
                    $setter = 'set' . Inflector::classify($associationName);
201
202
                    $associationDataContainerId = current($associationDataContainers);
203
                    $associationDataContainer = $index->getEntityById($associationDataContainerId);
204
                    $associationEntity = $this->getDoctrineEntityByDataContainer($associationDataContainer);
205
206
                    $rClass->getMethod($setter)->invoke($entity, $associationEntity);
207
                }
208
            }
209
        }
210
    }
211
212
    /**
213
     * Инциализация сущностей
214
     *
215
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\RuntimeException
216
     * @throws \UnexpectedValueException
217
     * @throws \Interop\Container\Exception\ContainerException
218
     * @throws \Interop\Container\Exception\NotFoundException
219
     */
220
    protected function initEntities()
221
    {
222
        $dataItems = $this->dataContainer->getIndex()->getEntities();
223
        foreach ($dataItems as $dataItem) {
224
            $this->initEntity($dataItem);
225
        }
226
    }
227
228
229
    /**
230
     * Инциализация сущности
231
     *
232
     * @param DataContainer\EntityInterface $dataItem
233
     *
234
     * @throws \UnexpectedValueException
235
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\RuntimeException
236
     * @throws \Interop\Container\Exception\ContainerException
237
     * @throws \Interop\Container\Exception\NotFoundException
238
     */
239
    protected function initEntity(DataContainer\EntityInterface $dataItem)
240
    {
241
        if ($this->isCandidateForSearchInDb($dataItem)) {
242
            $this->initEntityFromPersistenceStorage($dataItem);
243
        } else {
244
            $this->createEntity($dataItem);
245
        }
246
    }
247
248
    /**
249
     * Определяет нужно ли пытаться создать сущност заново, или необходим поиск в хранилище
250
     *
251
     * @param DataContainer\EntityInterface $dataItem
252
     *
253
     * @return bool
254
     */
255
    protected function isCandidateForSearchInDb(DataContainer\EntityInterface $dataItem)
256
    {
257
        $isCandidateForSearchInDb = $this->isFindById($dataItem);
258
        if (false === $isCandidateForSearchInDb) {
259
            $isCandidateForSearchInDb = 0 === count($dataItem->getAssociations());
260
        }
261
262
        return $isCandidateForSearchInDb;
263
    }
264
265
    /**
266
     * Проверяет можно ли искать сущность в хранилище по id
267
     *
268
     * @param DataContainer\EntityInterface $dataItem
269
     *
270
     * @return bool
271
     */
272
    protected function isFindById(DataContainer\EntityInterface $dataItem)
273
    {
274
        $entityClassName = $this->metadata->getEntityClassNameByDataContainer($dataItem);
275
        $entityMetadata = $this->objectManager->getClassMetadata($entityClassName);
276
277
        $identifierFieldNames = $entityMetadata->getIdentifierFieldNames();
278
        $isFundById = true;
279
280
        $properties = $dataItem->getProperties();
281
        foreach ($identifierFieldNames as $identifierFieldName) {
282
            if (!array_key_exists($identifierFieldName, $properties)) {
283
                $isFundById = false;
284
                break;
285
            }
286
        }
287
288
        return $isFundById;
289
    }
290
291
    /**
292
     * Подготавливает список id для поиска
293
     *
294
     * @param DataContainer\EntityInterface $dataItem
295
     *
296
     * @return array
297
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\RuntimeException
298
     */
299
    protected function buildSearchByIdCriteria(DataContainer\EntityInterface $dataItem)
300
    {
301
        $entityClassName = $this->metadata->getEntityClassNameByDataContainer($dataItem);
302
        $entityMetadata = $this->objectManager->getClassMetadata($entityClassName);
303
304
        $identifierFieldNames = $entityMetadata->getIdentifierFieldNames();
305
        $idValueList = [];
306
307
        $properties = $dataItem->getProperties();
308
        foreach ($identifierFieldNames as $identifierFieldName) {
309
            if (!array_key_exists($identifierFieldName, $properties)) {
310
                $errMsg = sprintf('Not find value for property %s from %s', $identifierFieldName, $entityClassName);
311
                throw new Exception\RuntimeException($errMsg);
312
            }
313
            $property = $properties[$identifierFieldName];
314
            $idValueList[$identifierFieldName] = $property->getValue();
315
        }
316
317
        return $idValueList;
318
    }
319
    /**
320
     * Ищет сущность в хранилище
321
     *
322
     * @param DataContainer\EntityInterface $dataItem
323
     *
324
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\RuntimeException
325
     * @throws \UnexpectedValueException
326
     * @throws \Interop\Container\Exception\ContainerException
327
     * @throws \Interop\Container\Exception\NotFoundException
328
     */
329
    protected function initEntityFromPersistenceStorage(DataContainer\EntityInterface $dataItem)
330
    {
331
        $entityClassName = $this->metadata->getEntityClassNameByDataContainer($dataItem);
332
        $entityRepository = $this->objectManager->getRepository($entityClassName);
333
334
335
        $findEntities = [];
336
        if ($this->isFindById($dataItem)) {
337
            $searchByIdCriteria = $this->buildSearchByIdCriteria($dataItem);
338
            $entity = $entityRepository->find($searchByIdCriteria);
339
            if (null !== $entity) {
340
                $findEntities[] = $entity;
341
            }
342
        } else {
343
            $searchCriteria = $this->buildSearchCriteria($dataItem);
344
            $findEntities = $entityRepository->findBy($searchCriteria);
345
        }
346
347
        $countFindEntities = count($findEntities);
348
        if ($countFindEntities > 1) {
349
            $errMsg = 'Found more than one entity';
350
            throw new Exception\RuntimeException($errMsg);
351
        }
352
353
        if (1 === $countFindEntities) {
354
            $this->dataContainerIdToDoctrineEntity[$dataItem->getId()] = array_pop($findEntities);
355
        } else {
356
            $this->createEntity($dataItem);
357
        }
358
    }
359
360
    /**
361
     * @param DataContainer\EntityInterface $dataItem
362
     *
363
     * @throws \Interop\Container\Exception\ContainerException
364
     * @throws \Interop\Container\Exception\NotFoundException
365
     */
366
    protected function createEntity(DataContainer\EntityInterface $dataItem)
367
    {
368
        $entityClassName = $this->metadata->getEntityClassNameByDataContainer($dataItem);
369
370
        $entity = $this->getEntityLocator()->get($entityClassName);
371
372
        $this->objectManager->persist($entity);
373
374
        $this->dataContainerIdToDoctrineEntity[$dataItem->getId()] = $entity;
375
    }
376
377
    /**
378
     * Получает сущность, которая соответствует контейнеру с данными
379
     *
380
     * @param DataContainer\EntityInterface $dataItem
381
     *
382
     * @return mixed
383
     * @throws \Nnx\JmsSerializerModule\DoctrineObjectEngine\Exception\RuntimeException
384
     */
385 View Code Duplication
    public function getDoctrineEntityByDataContainer(DataContainer\EntityInterface $dataItem)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
386
    {
387
        $dataItemId = $dataItem->getId();
388
        if (!array_key_exists($dataItemId, $this->dataContainerIdToDoctrineEntity)) {
389
            $errMsg = sprintf('Doctrine entity not found for data container: id# %s', $dataItemId);
390
            throw new Exception\RuntimeException($errMsg);
391
        }
392
393
        return $this->dataContainerIdToDoctrineEntity[$dataItemId];
394
    }
395
396
    /**
397
     * Подготовка критериев для поиска в базе данных
398
     *
399
     * @param DataContainer\EntityInterface $dataItem
400
     *
401
     * @return array
402
     */
403
    protected function buildSearchCriteria(DataContainer\EntityInterface $dataItem)
404
    {
405
        $searchCriteria = [];
406
407
        foreach ($dataItem->getProperties() as $property) {
408
            $normalizeName = Inflector::camelize($property->getName());
409
            $searchCriteria[$normalizeName] = $property->getValue();
410
        }
411
412
        return $searchCriteria;
413
    }
414
415
    /**
416
     * Устанавливает компонент отвечаюзий за создание сущностей
417
     *
418
     * @return ContainerInterface
419
     */
420
    public function getEntityLocator()
421
    {
422
        return $this->entityLocator;
423
    }
424
425
    /**
426
     * Возвращает компонент отвечаюзий за создание сущностей
427
     *
428
     * @param ContainerInterface $entityLocator
429
     *
430
     * @return $this
431
     */
432
    public function setEntityLocator(ContainerInterface $entityLocator)
433
    {
434
        $this->entityLocator = $entityLocator;
435
436
        return $this;
437
    }
438
}
439