ExtendConfigDumper::checkSchema()   F
last analyzed

Complexity

Conditions 25
Paths > 20000

Size

Total Lines 114
Code Lines 81

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 114
rs 2
cc 25
eloc 81
nc 409600
nop 4

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Oro\Bundle\EntityExtendBundle\Tools;
4
5
use Doctrine\Common\Cache\ClearableCache;
6
use Doctrine\ORM\Mapping\ClassMetadataFactory;
7
8
use Symfony\Component\Filesystem\Filesystem;
9
10
use Oro\Bundle\EntityConfigBundle\Config\ConfigInterface;
11
use Oro\Bundle\EntityConfigBundle\Config\ConfigManager;
12
use Oro\Bundle\EntityConfigBundle\Config\EntityManagerBag;
13
use Oro\Bundle\EntityConfigBundle\Config\Id\FieldConfigId;
14
use Oro\Bundle\EntityExtendBundle\EntityConfig\ExtendScope;
15
use Oro\Bundle\EntityExtendBundle\Extend\FieldTypeHelper;
16
use Oro\Bundle\EntityExtendBundle\Extend\RelationType;
17
use Oro\Bundle\EntityConfigBundle\Provider\ConfigProvider;
18
use Oro\Bundle\EntityExtendBundle\Tools\DumperExtensions\AbstractEntityConfigDumperExtension;
19
20
/**
21
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
22
 */
23
class ExtendConfigDumper
24
{
25
    const ACTION_PRE_UPDATE = 'preUpdate';
26
    const ACTION_POST_UPDATE = 'postUpdate';
27
28
    /** @deprecated Use ExtendHelper::getExtendEntityProxyClassName and ExtendHelper::ENTITY_NAMESPACE instead */
29
    const ENTITY = 'Extend\\Entity\\';
30
31
    const DEFAULT_PREFIX = 'default_';
32
33
    /** @var string */
34
    protected $cacheDir;
35
36
    /** @var EntityManagerBag */
37
    protected $entityManagerBag;
38
39
    /** @var ConfigManager */
40
    protected $configManager;
41
42
    /** @var ExtendDbIdentifierNameGenerator */
43
    protected $nameGenerator;
44
45
    /** @var FieldTypeHelper */
46
    protected $fieldTypeHelper;
47
48
    /** @var EntityGenerator */
49
    protected $entityGenerator;
50
51
    /** @var array */
52
    protected $extensions = [];
53
54
    /** @var AbstractEntityConfigDumperExtension[]|null */
55
    protected $sortedExtensions;
56
57
    /**
58
     * @param EntityManagerBag                $entityManagerBag
59
     * @param ConfigManager                   $configManager
60
     * @param ExtendDbIdentifierNameGenerator $nameGenerator
61
     * @param FieldTypeHelper                 $fieldTypeHelper
62
     * @param EntityGenerator                 $entityGenerator
63
     * @param string                          $cacheDir
64
     */
65
    public function __construct(
66
        EntityManagerBag $entityManagerBag,
67
        ConfigManager $configManager,
68
        ExtendDbIdentifierNameGenerator $nameGenerator,
69
        FieldTypeHelper $fieldTypeHelper,
70
        EntityGenerator $entityGenerator,
71
        $cacheDir
72
    ) {
73
        $this->entityManagerBag = $entityManagerBag;
74
        $this->configManager    = $configManager;
75
        $this->nameGenerator    = $nameGenerator;
76
        $this->fieldTypeHelper  = $fieldTypeHelper;
77
        $this->entityGenerator  = $entityGenerator;
78
        $this->cacheDir         = $cacheDir;
79
    }
80
81
    /**
82
     * Gets the cache directory
83
     *
84
     * @return string
85
     */
86
    public function getCacheDir()
87
    {
88
        return $this->cacheDir;
89
    }
90
91
    /**
92
     * Sets the cache directory
93
     *
94
     * @param string $cacheDir
95
     */
96
    public function setCacheDir($cacheDir)
97
    {
98
        $this->cacheDir = $cacheDir;
99
    }
100
101
    /**
102
     * @param AbstractEntityConfigDumperExtension $extension
103
     * @param int                                 $priority
104
     */
105
    public function addExtension(AbstractEntityConfigDumperExtension $extension, $priority = 0)
106
    {
107
        $this->extensions[$priority][] = $extension;
108
    }
109
110
    /**
111
     * Update config.
112
     *
113
     * @param callable|null $filter function (ConfigInterface $config) : bool
114
     * @param bool $updateCustom
115
     */
116
    public function updateConfig($filter = null, $updateCustom = false)
117
    {
118
        $aliases = ExtendClassLoadingUtils::getAliases($this->cacheDir);
119
        $this->clear(true);
120
121
        if ($updateCustom) {
122
            $this->updatePendingConfigs();
123
        }
124
125
        $extensions = $this->getExtensions();
126
127
        foreach ($extensions as $extension) {
128
            if ($extension->supports(self::ACTION_PRE_UPDATE)) {
129
                $extension->preUpdate();
130
            }
131
        }
132
133
        $configProvider = $this->configManager->getProvider('extend');
134
135
        $extendConfigs = null === $filter
136
            ? $configProvider->getConfigs(null, true)
137
            : $configProvider->filter($filter, null, true);
138
        foreach ($extendConfigs as $extendConfig) {
139
            if ($extendConfig->is('upgradeable')) {
140
                if ($extendConfig->is('is_extend')) {
141
                    $this->checkSchema($extendConfig, $configProvider, $aliases, $filter);
0 ignored issues
show
Bug introduced by
It seems like $configProvider defined by $this->configManager->getProvider('extend') on line 133 can be null; however, Oro\Bundle\EntityExtendB...igDumper::checkSchema() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
142
                }
143
144
                $this->updateStateValues($extendConfig, $configProvider);
0 ignored issues
show
Bug introduced by
It seems like $configProvider defined by $this->configManager->getProvider('extend') on line 133 can be null; however, Oro\Bundle\EntityExtendB...er::updateStateValues() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
145
            }
146
        }
147
148
        foreach ($extensions as $extension) {
149
            if ($extension->supports(self::ACTION_POST_UPDATE)) {
150
                $extension->postUpdate();
151
            }
152
        }
153
154
        $this->configManager->flush();
155
156
        $this->clear();
157
    }
158
159
    public function dump()
160
    {
161
        $schemas       = [];
162
        $extendConfigs = $this->configManager->getProvider('extend')->getConfigs(null, true);
163
        foreach ($extendConfigs as $extendConfig) {
164
            $schema    = $extendConfig->get('schema');
165
            $className = $extendConfig->getId()->getClassName();
166
167
            if ($schema) {
168
                $schemas[$className]                 = $schema;
169
                $schemas[$className]['relationData'] = $extendConfig->get('relation', false, []);
170
            }
171
        }
172
173
        $cacheDir = $this->entityGenerator->getCacheDir();
174
        if ($cacheDir === $this->cacheDir) {
175
            $this->entityGenerator->generate($schemas);
176
        } else {
177
            $this->entityGenerator->setCacheDir($this->cacheDir);
178
            try {
179
                $this->entityGenerator->generate($schemas);
180
                $this->entityGenerator->setCacheDir($cacheDir);
181
            } catch (\Exception $e) {
182
                $this->entityGenerator->setCacheDir($cacheDir);
183
                throw $e;
184
            }
185
        }
186
    }
187
188
    /**
189
     * Makes sure that extended entity configs are ready to be processing by other config related commands
190
     */
191
    public function checkConfig()
192
    {
193
        $hasAliases = file_exists(ExtendClassLoadingUtils::getAliasesPath($this->cacheDir));
194
        if ($hasAliases) {
195
            return;
196
        }
197
198
        $hasChanges    = false;
199
        $extendConfigs = $this->configManager->getProvider('extend')->getConfigs(null, true);
200
        foreach ($extendConfigs as $extendConfig) {
201
            $schema = $extendConfig->get('schema', false, []);
202
203
            // make sure that inheritance definition for extended entities is up-to-date
204
            // this check is required to avoid doctrine mapping exceptions during the platform update
205
            // in case if a parent class is changed in a new version of a code
206
            if (isset($schema['type'], $schema['class'], $schema['entity']) && $schema['type'] === 'Extend') {
207
                $entityClassName = $schema['entity'];
208
                $parentClassName = get_parent_class($schema['class']);
209
                if ($parentClassName !== $entityClassName) {
210
                    $inheritClassName = get_parent_class($parentClassName);
211
212
                    $hasSchemaChanges = false;
213 View Code Duplication
                    if (!isset($schema['parent']) || $schema['parent'] !== $parentClassName) {
214
                        $hasSchemaChanges = true;
215
                        $schema['parent'] = $parentClassName;
216
                    }
217 View Code Duplication
                    if (!isset($schema['inherit']) || $schema['inherit'] !== $inheritClassName) {
218
                        $hasSchemaChanges  = true;
219
                        $schema['inherit'] = $inheritClassName;
220
                    }
221
222
                    if ($hasSchemaChanges) {
223
                        $hasChanges = true;
224
                        $extendConfig->set('schema', $schema);
225
                        $this->configManager->persist($extendConfig);
226
                    }
227
                }
228
            }
229
        }
230
231
        if ($hasChanges) {
232
            $this->configManager->flush();
233
        }
234
    }
235
236
    /**
237
     * Removes the entity proxies and metadata from the cache
238
     *
239
     * @param bool $keepEntityProxies Set TRUE if proxies for custom and extend entities should not be deleted
240
     */
241
    public function clear($keepEntityProxies = false)
242
    {
243
        $filesystem = new Filesystem();
244
245
        if ($keepEntityProxies) {
246
            $aliasesPath = ExtendClassLoadingUtils::getAliasesPath($this->cacheDir);
247
            if ($filesystem->exists($aliasesPath)) {
248
                $filesystem->remove($aliasesPath);
249
            }
250
        } else {
251
            $baseCacheDir = ExtendClassLoadingUtils::getEntityBaseCacheDir($this->cacheDir);
252
            if ($filesystem->exists($baseCacheDir)) {
253
                $filesystem->remove([$baseCacheDir]);
254
            }
255
            $filesystem->mkdir(ExtendClassLoadingUtils::getEntityCacheDir($this->cacheDir));
256
        }
257
258
        foreach ($this->entityManagerBag->getEntityManagers() as $em) {
259
            /** @var ClassMetadataFactory $metadataFactory */
260
            $metadataFactory = $em->getMetadataFactory();
261
            $metadataCache   = $metadataFactory->getCacheDriver();
262
            if ($metadataCache instanceof ClearableCache) {
263
                $metadataCache->deleteAll();
264
            }
265
        }
266
    }
267
268
    /**
269
     * Return sorted extensions
270
     *
271
     * @return AbstractEntityConfigDumperExtension[]
272
     */
273 View Code Duplication
    protected function getExtensions()
274
    {
275
        if (null === $this->sortedExtensions) {
276
            krsort($this->extensions);
277
            $this->sortedExtensions = call_user_func_array('array_merge', $this->extensions);
278
        }
279
280
        return $this->sortedExtensions;
281
    }
282
283
    /**
284
     * Check fields parameters and update field config
285
     *
286
     * @param string          $entityName
287
     * @param ConfigInterface $fieldConfig
288
     * @param array           $relationProperties
289
     * @param array           $defaultProperties
290
     * @param array           $properties
291
     * @param array           $doctrine
292
     */
293
    protected function checkFieldSchema(
294
        $entityName,
295
        ConfigInterface $fieldConfig,
296
        array &$relationProperties,
297
        array &$defaultProperties,
298
        array &$properties,
299
        array &$doctrine
300
    ) {
301
        if ($fieldConfig->is('is_extend')) {
302
            /** @var FieldConfigId $fieldConfigId */
303
            $fieldConfigId = $fieldConfig->getId();
304
            $fieldName     = $fieldConfigId->getFieldName();
305
            $fieldType     = $fieldConfigId->getFieldType();
306
            $isDeleted     = $fieldConfig->is('is_deleted');
307
            $columnName    = $fieldConfig->get('column_name', false, $fieldName);
308
309
            $underlyingFieldType = $this->fieldTypeHelper->getUnderlyingType($fieldType);
310
            if (in_array($underlyingFieldType, RelationType::$anyToAnyRelations, true)) {
311
                $relationProperties[$fieldName] = [];
312
                if ($isDeleted) {
313
                    $relationProperties[$fieldName]['private'] = true;
314
                }
315
                if ($underlyingFieldType !== RelationType::MANY_TO_ONE && !$fieldConfig->is('without_default')) {
316
                    $defaultName = self::DEFAULT_PREFIX . $fieldName;
317
318
                    $defaultProperties[$defaultName] = [];
319
                    if ($isDeleted) {
320
                        $defaultProperties[$defaultName]['private'] = true;
321
                    }
322
                }
323
            } else {
324
                $properties[$fieldName] = [];
325
                if ($isDeleted) {
326
                    $properties[$fieldName]['private'] = true;
327
                }
328
329
                $doctrine[$entityName]['fields'][$fieldName] = [
330
                    'column'    => $columnName,
331
                    'type'      => $fieldType,
332
                    'nullable'  => true,
333
                    'length'    => $fieldConfig->get('length'),
334
                    'precision' => $fieldConfig->get('precision'),
335
                    'scale'     => $fieldConfig->get('scale'),
336
                ];
337
            }
338
        }
339
    }
340
341
    /**
342
     * @SuppressWarnings(PHPMD.NPathComplexity)
343
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
344
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
345
     *
346
     * @param ConfigInterface $extendConfig
347
     * @param ConfigProvider  $configProvider
348
     * @param array|null      $aliases
349
     * @param callable|null   $filter function (ConfigInterface $config) : bool
350
     */
351
    protected function checkSchema(
352
        ConfigInterface $extendConfig,
353
        ConfigProvider $configProvider,
354
        $aliases,
355
        $filter = null
356
    ) {
357
        $className  = $extendConfig->getId()->getClassName();
358
        $doctrine   = [];
359
        $entityName = $className;
360
361
        if (ExtendHelper::isCustomEntity($className)) {
362
            $type      = 'Custom';
363
            $tableName = $extendConfig->get('table');
364
            if (!$tableName) {
365
                $tableName = $this->nameGenerator->generateCustomEntityTableName($className);
366
            }
367
            $doctrine[$entityName] = [
368
                'type'  => 'entity',
369
                'table' => $tableName
370
            ];
371
            // add 'id' field only for Custom entity without inheritance
372
            if (!$extendConfig->has('inherit')) {
373
                $doctrine[$entityName]['fields'] = [
374
                    'id' => ['type' => 'integer', 'id' => true, 'generator' => ['strategy' => 'AUTO']]
375
                ];
376
            }
377
        } else {
378
            $type                  = 'Extend';
379
            $entityName            = $extendConfig->get('extend_class');
380
            $doctrine[$entityName] = [
381
                'type'   => 'mappedSuperclass',
382
                'fields' => [],
383
            ];
384
        }
385
386
        $schema             = $extendConfig->get('schema', false, []);
387
        $properties         = isset($schema['property']) && null !== $filter ? $schema['property'] : [];
388
        $relationProperties = isset($schema['relation']) && null !== $filter ? $schema['relation'] : [];
389
        $defaultProperties  = isset($schema['default']) && null !== $filter ? $schema['default'] : [];
390
        $addRemoveMethods   = isset($schema['addremove']) && null !== $filter ? $schema['addremove'] : [];
391
392
        $fieldConfigs = null === $filter
393
            ? $configProvider->getConfigs($className, true)
394
            : $configProvider->filter($filter, $className, true);
395
        foreach ($fieldConfigs as $fieldConfig) {
396
            $this->updateFieldState($fieldConfig);
397
            $this->checkFieldSchema(
398
                $entityName,
399
                $fieldConfig,
400
                $relationProperties,
401
                $defaultProperties,
402
                $properties,
403
                $doctrine
404
            );
405
        }
406
407
        $relations = $extendConfig->get('relation', false, []);
408
        foreach ($relations as $relation) {
409
            /** @var FieldConfigId $fieldId */
410
            $fieldId = $relation['field_id'];
411
            if (!$fieldId) {
412
                continue;
413
            }
414
415
            $fieldName = $fieldId->getFieldName();
416
            $isDeleted = $configProvider->hasConfig($fieldId->getClassName(), $fieldName)
417
                ? $configProvider->getConfig($fieldId->getClassName(), $fieldName)->is('is_deleted')
418
                : false;
419
            if (!isset($relationProperties[$fieldName])) {
420
                $relationProperties[$fieldName] = [];
421
                if ($isDeleted) {
422
                    $relationProperties[$fieldName]['private'] = true;
423
                }
424
            }
425
            if (!$isDeleted && $fieldId->getFieldType() !== RelationType::MANY_TO_ONE) {
426
                $addRemoveMethods[$fieldName]['self'] = $fieldName;
427
428
                /** @var FieldConfigId $targetFieldId */
429
                $targetFieldId = $relation['target_field_id'];
430
                if ($targetFieldId) {
431
                    $fieldType = $fieldId->getFieldType();
432
433
                    $addRemoveMethods[$fieldName]['target']              = $targetFieldId->getFieldName();
434
                    $addRemoveMethods[$fieldName]['is_target_addremove'] = $fieldType === RelationType::MANY_TO_MANY;
435
                }
436
            }
437
        }
438
439
        $schema = [
440
            'class'     => $className,
441
            'entity'    => $entityName,
442
            'type'      => $type,
443
            'property'  => $properties,
444
            'relation'  => $relationProperties,
445
            'default'   => $defaultProperties,
446
            'addremove' => $addRemoveMethods,
447
            'doctrine'  => $doctrine,
448
        ];
449
450
        if ($type === 'Extend') {
451
            $parentClassName = get_parent_class($className);
452
            if ($parentClassName === $entityName) {
453
                $parentClassName = $aliases[$entityName];
454
            }
455
            $schema['parent']  = $parentClassName;
456
            $schema['inherit'] = get_parent_class($parentClassName);
457
        } elseif ($extendConfig->has('inherit')) {
458
            $schema['inherit'] = $extendConfig->get('inherit');
459
        }
460
461
        $extendConfig->set('schema', $schema);
462
463
        $this->configManager->persist($extendConfig);
464
    }
465
466
    /**
467
     * @param ConfigInterface $fieldConfig
468
     */
469
    protected function updateFieldState(ConfigInterface $fieldConfig)
470
    {
471
        if ($fieldConfig->is('state', ExtendScope::STATE_DELETE)) {
472
            $fieldConfig->set('is_deleted', true);
473
            $this->configManager->persist($fieldConfig);
474
        } elseif (!$fieldConfig->is('state', ExtendScope::STATE_ACTIVE)) {
475
            if ($fieldConfig->is('state', ExtendScope::STATE_RESTORE)) {
476
                $fieldConfig->set('is_deleted', false);
477
            }
478
            $fieldConfig->set('state', ExtendScope::STATE_ACTIVE);
479
            $this->configManager->persist($fieldConfig);
480
        }
481
    }
482
483
    /**
484
     * @param ConfigInterface $entityConfig
485
     * @param ConfigProvider  $configProvider
486
     */
487
    protected function updateStateValues(ConfigInterface $entityConfig, ConfigProvider $configProvider)
488
    {
489
        if ($entityConfig->is('state', ExtendScope::STATE_DELETE)) {
490
            // mark entity as deleted
491
            if (!$entityConfig->is('is_deleted')) {
492
                $entityConfig->set('is_deleted', true);
493
                $this->configManager->persist($entityConfig);
494
            }
495
496
            // mark all fields as deleted
497
            $fieldConfigs = $configProvider->getConfigs($entityConfig->getId()->getClassName(), true);
498
            foreach ($fieldConfigs as $fieldConfig) {
499
                if (!$fieldConfig->is('is_deleted')) {
500
                    $fieldConfig->set('is_deleted', true);
501
                    $this->configManager->persist($fieldConfig);
502
                }
503
            }
504
        } elseif (!$entityConfig->is('state', ExtendScope::STATE_ACTIVE)) {
505
            $hasNotActiveFields = false;
506
507
            $fieldConfigs = $configProvider->getConfigs($entityConfig->getId()->getClassName(), true);
508
            foreach ($fieldConfigs as $fieldConfig) {
509
                if (!$fieldConfig->in('state', [ExtendScope::STATE_ACTIVE, ExtendScope::STATE_DELETE])) {
510
                    $hasNotActiveFields = true;
511
                    break;
512
                }
513
            }
514
515
            // Set entity state to active if all fields are active or deleted
516
            if (!$hasNotActiveFields) {
517
                $entityConfig->set('state', ExtendScope::STATE_ACTIVE);
518
                $this->configManager->persist($entityConfig);
519
            }
520
        }
521
    }
522
523
    /**
524
     * Updates pending configs
525
     */
526
    protected function updatePendingConfigs()
527
    {
528
        $pendingChanges = [];
529
530
        $configs = $this->configManager->getProvider('extend')->getConfigs();
531
        foreach ($configs as $config) {
532
            $configPendingChanges = $config->get('pending_changes');
533
            if (!$configPendingChanges) {
534
                continue;
535
            }
536
537
            $pendingChanges[$config->getId()->getClassName()] = $configPendingChanges;
538
            $config->set('pending_changes', []);
539
            $this->configManager->persist($config);
540
        }
541
542
        foreach ($pendingChanges as $className => $changes) {
543
            foreach ($changes as $scope => $values) {
544
                $provider = $this->configManager->getProvider($scope);
545
                $config = $provider->getConfig($className);
546
                foreach ($values as $code => $value) {
547
                    $config->set($code, ExtendHelper::updatedPendingValue($config->get($code), $value));
548
                }
549
                $this->configManager->persist($config);
550
            }
551
        }
552
    }
553
}
554