EntityFieldProvider   F
last analyzed

Complexity

Total Complexity 94

Size/Duplication

Total Lines 759
Duplicated Lines 2.24 %

Coupling/Cohesion

Components 1
Dependencies 17
Metric Value
wmc 94
lcom 1
cbo 17
dl 17
loc 759
rs 1.263

27 Methods

Rating   Name   Duplication   Size   Complexity  
A setEntityProvider() 0 4 1
A setVirtualFieldProvider() 0 4 1
A setVirtualRelationProvider() 0 4 1
A setExclusionProvider() 0 4 1
A getMetadataFor() 0 4 1
A getRelationType() 0 4 1
A sortFields() 0 18 4
A isIgnoredEntity() 0 4 1
A isIgnoredRelation() 0 4 1
A __construct() 17 17 1
A getRelations() 0 17 2
B getFields() 0 43 6
C addFields() 0 40 8
D addVirtualFields() 0 33 10
B addVirtualRelations() 0 32 6
A addField() 0 12 3
C addRelations() 0 42 8
C addUnidirectionalRelations() 0 47 8
D getUnidirectionalRelations() 0 30 9
B addRelation() 0 32 4
A addEntityDetails() 0 11 3
A isEntityAccessible() 0 6 2
A isFieldAccessible() 0 6 2
A isIgnoredField() 0 9 2
B getFieldLabel() 0 16 5
A getRelationFieldType() 0 7 1
A getManagerForClass() 0 11 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like EntityFieldProvider often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use EntityFieldProvider, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Oro\Bundle\EntityBundle\Provider;
4
5
use Symfony\Bridge\Doctrine\ManagerRegistry;
6
use Symfony\Component\Translation\TranslatorInterface;
7
8
use Doctrine\Common\Persistence\Mapping\ClassMetadata as ClassMetadataInterface;
9
use Doctrine\ORM\EntityManager;
10
use Doctrine\ORM\Mapping\ClassMetadata;
11
use Doctrine\ORM\Mapping\ClassMetadataInfo;
12
13
use Oro\Bundle\EntityBundle\ORM\EntityClassResolver;
14
use Oro\Bundle\EntityBundle\Exception\InvalidEntityException;
15
use Oro\Bundle\EntityConfigBundle\Provider\ConfigProvider;
16
use Oro\Bundle\EntityConfigBundle\Config\Id\FieldConfigId;
17
use Oro\Bundle\EntityConfigBundle\Tools\ConfigHelper;
18
use Oro\Bundle\EntityExtendBundle\Extend\FieldTypeHelper;
19
use Oro\Bundle\EntityExtendBundle\Tools\ExtendHelper;
20
21
/**
22
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
23
 * TODO: passing parameter $applyExclusions into getFields method should be refactored
24
 */
25
class EntityFieldProvider
26
{
27
    /** @var EntityProvider */
28
    protected $entityProvider;
29
30
    /** @var VirtualFieldProviderInterface */
31
    protected $virtualFieldProvider;
32
33
    /** @var VirtualRelationProviderInterface */
34
    protected $virtualRelationProvider;
35
36
    /** @var ExclusionProviderInterface */
37
    protected $exclusionProvider;
38
39
    /** @var ConfigProvider */
40
    protected $entityConfigProvider;
41
42
    /** @var ConfigProvider */
43
    protected $extendConfigProvider;
44
45
    /** @var EntityClassResolver */
46
    protected $entityClassResolver;
47
48
    /** @var FieldTypeHelper */
49
    protected $fieldTypeHelper;
50
51
    /** @var TranslatorInterface */
52
    protected $translator;
53
54
    /** @var ManagerRegistry */
55
    protected $doctrine;
56
57
    /** @var array */
58
    protected $hiddenFields;
59
60
    /**
61
     * Constructor
62
     *
63
     * @param ConfigProvider      $entityConfigProvider
64
     * @param ConfigProvider      $extendConfigProvider
65
     * @param EntityClassResolver $entityClassResolver
66
     * @param FieldTypeHelper     $fieldTypeHelper
67
     * @param ManagerRegistry     $doctrine
68
     * @param TranslatorInterface $translator
69
     * @param array               $hiddenFields
70
     */
71 View Code Duplication
    public function __construct(
72
        ConfigProvider $entityConfigProvider,
73
        ConfigProvider $extendConfigProvider,
74
        EntityClassResolver $entityClassResolver,
75
        FieldTypeHelper $fieldTypeHelper,
76
        ManagerRegistry $doctrine,
77
        TranslatorInterface $translator,
78
        $hiddenFields
79
    ) {
80
        $this->entityConfigProvider = $entityConfigProvider;
81
        $this->extendConfigProvider = $extendConfigProvider;
82
        $this->entityClassResolver  = $entityClassResolver;
83
        $this->fieldTypeHelper      = $fieldTypeHelper;
84
        $this->doctrine             = $doctrine;
85
        $this->translator           = $translator;
86
        $this->hiddenFields         = $hiddenFields;
87
    }
88
89
    /**
90
     * Sets entity provider
91
     *
92
     * @param EntityProvider $entityProvider
93
     */
94
    public function setEntityProvider(EntityProvider $entityProvider)
95
    {
96
        $this->entityProvider = $entityProvider;
97
    }
98
99
    /**
100
     * Sets virtual field provider
101
     *
102
     * @param VirtualFieldProviderInterface $virtualFieldProvider
103
     */
104
    public function setVirtualFieldProvider(VirtualFieldProviderInterface $virtualFieldProvider)
105
    {
106
        $this->virtualFieldProvider = $virtualFieldProvider;
107
    }
108
109
    /**
110
     * @param VirtualRelationProviderInterface $virtualRelationProvider
111
     */
112
    public function setVirtualRelationProvider($virtualRelationProvider)
113
    {
114
        $this->virtualRelationProvider = $virtualRelationProvider;
115
    }
116
117
    /**
118
     * Sets exclusion provider
119
     *
120
     * @param ExclusionProviderInterface $exclusionProvider
121
     */
122
    public function setExclusionProvider(ExclusionProviderInterface $exclusionProvider)
123
    {
124
        $this->exclusionProvider = $exclusionProvider;
125
    }
126
127
    /**
128
     * Returns relations for the given entity
129
     *
130
     * @param string $entityName         Entity name. Can be full class name or short form: Bundle:Entity.
131
     * @param bool   $applyExclusions    Indicates whether exclusion logic should be applied.
132
     * @param bool   $withEntityDetails  Indicates whether details of related entity should be returned as well.
133
     * @param bool   $translate          Flag means that label, plural label should be translated
134
     *                                   .       'name'          - field name
135
     *                                   .       'type'          - field type
136
     *                                   .       'label'         - field label
137
     *                                   .       'related_entity_name' - entity full class name
138
     *                                   .       'relation_type'       - relation type
139
     *                                   If $withEntityDetails = true the following attributes are added:
140
     *                                   .       'related_entity_label'        - entity label
141
     *                                   .       'related_entity_plural_label' - entity plural label
142
     *                                   .       'related_entity_icon'         - an icon associated with an entity
143
     *
144
     * @return array of relations
145
     */
146
    public function getRelations(
147
        $entityName,
148
        $withEntityDetails = false,
149
        $applyExclusions = true,
150
        $translate = true
151
    ) {
152
        $className = $this->entityClassResolver->getEntityClass($entityName);
153
        if (!$this->isEntityAccessible($className)) {
154
            return [];
155
        }
156
157
        $result = [];
158
159
        $this->addRelations($result, $className, $withEntityDetails, $applyExclusions, $translate);
160
161
        return $result;
162
    }
163
164
    /**
165
     * Returns fields for the given entity
166
     *
167
     * @param string $entityName         Entity name. Can be full class name or short form: Bundle:Entity.
168
     * @param bool   $withRelations      Indicates whether association fields should be returned as well.
169
     * @param bool   $withVirtualFields  Indicates whether virtual fields should be returned as well.
170
     * @param bool   $withEntityDetails  Indicates whether details of related entity should be returned as well.
171
     * @param bool   $withUnidirectional Indicates whether Unidirectional association fields should be returned.
172
     * @param bool   $applyExclusions    Indicates whether exclusion logic should be applied.
173
     * @param bool   $translate          Flag means that label, plural label should be translated
174
     *
175
     * @return array of fields sorted by field label (relations follows fields)
176
     *                                   .       'name'          - field name
177
     *                                   .       'type'          - field type
178
     *                                   .       'label'         - field label
179
     *                                   If a field is an identifier (primary key in terms of a database)
180
     *                                   .       'identifier'    - true for an identifier field
181
     *                                   If a field represents a relation and $withRelations = true or
182
     *                                   a virtual field has 'filter_by_id' = true following attribute is added:
183
     *                                   .       'related_entity_name' - entity full class name
184
     *                                   If a field represents a relation and $withRelations = true
185
     *                                   the following attributes are added:
186
     *                                   .       'relation_type'       - relation type
187
     *                                   If a field represents a relation and $withEntityDetails = true
188
     *                                   the following attributes are added:
189
     *                                   .       'related_entity_label'        - entity label
190
     *                                   .       'related_entity_plural_label' - entity plural label
191
     *                                   .       'related_entity_icon'         - an icon associated with an entity
192
     */
193
    public function getFields(
194
        $entityName,
195
        $withRelations = false,
196
        $withVirtualFields = false,
197
        $withEntityDetails = false,
198
        $withUnidirectional = false,
199
        $applyExclusions = true,
200
        $translate = true
201
    ) {
202
        $className = $this->entityClassResolver->getEntityClass($entityName);
203
        if (!$this->isEntityAccessible($className)) {
204
            return [];
205
        }
206
207
        $result = [];
208
209
        $this->addFields($result, $className, $applyExclusions, $translate);
210
211
        if ($withVirtualFields) {
212
            $this->addVirtualFields($result, $className, $applyExclusions, $translate);
213
        }
214
215
        if ($withRelations) {
216
            $this->addRelations($result, $className, $withEntityDetails, $applyExclusions, $translate);
217
218
            if ($withVirtualFields) {
219
                $this->addVirtualRelations($result, $className, $withEntityDetails, $applyExclusions, $translate);
220
            }
221
222
            if ($withUnidirectional) {
223
                $this->addUnidirectionalRelations(
224
                    $result,
225
                    $className,
226
                    $withEntityDetails,
227
                    $applyExclusions,
228
                    $translate
229
                );
230
            }
231
        }
232
        $this->sortFields($result);
233
234
        return $result;
235
    }
236
237
    /**
238
     * Adds entity fields to $result
239
     *
240
     * @param array         $result
241
     * @param string        $className
242
     * @param bool          $applyExclusions
243
     * @param bool          $translate
244
     */
245
    protected function addFields(array &$result, $className, $applyExclusions, $translate)
246
    {
247
        $metadata = $this->getMetadataFor($className);
248
249
        // add regular fields
250
        $configs = $this->extendConfigProvider->getConfigs($className);
251
        foreach ($configs as $fieldConfig) {
252
            /** @var FieldConfigId $fieldConfigId */
253
            $fieldConfigId = $fieldConfig->getId();
254
            $fieldName = $fieldConfigId->getFieldName();
255
256
            $underlyingFieldType = $this->fieldTypeHelper->getUnderlyingType($fieldConfigId->getFieldType());
257
            if ($this->fieldTypeHelper->isRelation($underlyingFieldType)) {
258
                // skip because this field is relation
259
                continue;
260
            }
261
            if (isset($result[$fieldName])) {
262
                // skip because a field with this name is already added, it could be a virtual field
263
                continue;
264
            }
265
            if (!ExtendHelper::isFieldAccessible($fieldConfig)) {
266
                continue;
267
            }
268
            if ($this->isIgnoredField($metadata, $fieldName)) {
269
                continue;
270
            }
271
            if ($applyExclusions && $this->exclusionProvider->isIgnoredField($metadata, $fieldName)) {
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
272
                continue;
273
            }
274
275
            $this->addField(
276
                $result,
277
                $fieldName,
278
                $fieldConfigId->getFieldType(),
279
                $this->getFieldLabel($metadata, $fieldName),
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
280
                $metadata->isIdentifier($fieldName),
281
                $translate
282
            );
283
        }
284
    }
285
286
    /**
287
     * Adds entity virtual fields to $result
288
     *
289
     * @param array  $result
290
     * @param string $className
291
     * @param bool   $applyExclusions
292
     * @param bool   $translate
293
     */
294
    protected function addVirtualFields(array &$result, $className, $applyExclusions, $translate)
295
    {
296
        if (!$this->virtualFieldProvider) {
297
            return;
298
        }
299
300
        $metadata = $this->getMetadataFor($className);
301
        $virtualFields = $this->virtualFieldProvider->getVirtualFields($className);
302
        foreach ($virtualFields as $fieldName) {
303
            if ($applyExclusions && $this->exclusionProvider->isIgnoredField($metadata, $fieldName)) {
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
304
                continue;
305
            }
306
307
            $query      = $this->virtualFieldProvider->getVirtualFieldQuery($className, $fieldName);
308
            $fieldLabel = !empty($query['select']['label'])
309
                ? $query['select']['label']
310
                : $this->getFieldLabel($metadata, $fieldName);
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
311
312
            $this->addField(
313
                $result,
314
                $fieldName,
315
                $query['select']['return_type'],
316
                $fieldLabel,
317
                false,
318
                $translate
319
            );
320
            if (isset($query['select']['related_entity_name']) && $query['select']['related_entity_name']) {
321
                $result[$fieldName]['related_entity_name'] = $query['select']['related_entity_name'];
322
            } elseif (isset($query['select']['filter_by_id']) && $query['select']['filter_by_id']) {
323
                $result[$fieldName]['related_entity_name'] = $metadata->getAssociationTargetClass($fieldName);
324
            }
325
        }
326
    }
327
328
    /**
329
     * Adds entity virtual fields to $result
330
     *
331
     * @param array  $result
332
     * @param string $className
333
     * @param bool $withEntityDetails
334
     * @param bool $applyExclusions
335
     * @param bool $translate
336
     */
337
    protected function addVirtualRelations(array &$result, $className, $withEntityDetails, $applyExclusions, $translate)
338
    {
339
        if (!$this->virtualRelationProvider) {
340
            return;
341
        }
342
343
        $metadata         = $this->getMetadataFor($className);
344
        $virtualRelations = $this->virtualRelationProvider->getVirtualRelations($className);
345
        foreach ($virtualRelations as $associationName => $virtualRelation) {
346
            if ($applyExclusions && $this->exclusionProvider->isIgnoredField($metadata, $associationName)) {
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
347
                continue;
348
            }
349
350
            $fieldType       = $virtualRelation['relation_type'];
351
            $targetClassName = $this->entityClassResolver->getEntityClass($virtualRelation['related_entity_name']);
352
353
            $label = !empty($virtualRelation['label'])
354
                ? $virtualRelation['label']
355
                : $this->getFieldLabel($metadata, $associationName);
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
356
357
            $this->addRelation(
358
                $result,
359
                $associationName,
360
                $fieldType,
361
                $label,
362
                $this->getRelationType($fieldType),
363
                $targetClassName,
364
                $withEntityDetails,
365
                $translate
366
            );
367
        }
368
    }
369
370
    /**
371
     * Adds a field to $result
372
     *
373
     * @param array  $result
374
     * @param string $name
375
     * @param string $type
376
     * @param string $label
377
     * @param bool   $isIdentifier
378
     * @param bool   $translate
379
     */
380
    protected function addField(array &$result, $name, $type, $label, $isIdentifier, $translate)
381
    {
382
        $field = [
383
            'name'  => $name,
384
            'type'  => $type,
385
            'label' => $translate ? $this->translator->trans($label) : $label
386
        ];
387
        if ($isIdentifier) {
388
            $field['identifier'] = true;
389
        }
390
        $result[$name] = $field;
391
    }
392
393
    /**
394
     * Adds entity relations to $result
395
     *
396
     * @param array         $result
397
     * @param string        $className
398
     * @param bool          $withEntityDetails
399
     * @param bool          $applyExclusions
400
     * @param bool          $translate
401
     */
402
    protected function addRelations(
403
        array &$result,
404
        $className,
405
        $withEntityDetails,
406
        $applyExclusions,
407
        $translate
408
    ) {
409
        $metadata         = $this->getMetadataFor($className);
410
        $associationNames = $metadata->getAssociationNames();
411
        foreach ($associationNames as $associationName) {
412
            if (isset($result[$associationName])) {
413
                // skip because a relation with this name is already added, it could be a virtual field
414
                continue;
415
            }
416
            if (!$this->isFieldAccessible($className, $associationName)) {
417
                continue;
418
            }
419
            $targetClassName = $metadata->getAssociationTargetClass($associationName);
420
            if (!$this->isEntityAccessible($targetClassName)) {
421
                continue;
422
            }
423
            if ($this->isIgnoredRelation($metadata, $associationName)) {
424
                continue;
425
            }
426
            if ($applyExclusions && $this->exclusionProvider->isIgnoredRelation($metadata, $associationName)) {
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
427
                continue;
428
            }
429
430
            $fieldType = $this->getRelationFieldType($className, $associationName);
431
432
            $this->addRelation(
433
                $result,
434
                $associationName,
435
                $fieldType,
436
                $this->getFieldLabel($metadata, $associationName),
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
437
                $this->getRelationType($fieldType),
438
                $targetClassName,
439
                $withEntityDetails,
440
                $translate
441
            );
442
        }
443
    }
444
445
    /**
446
     * Adds remote entities relations to $className entity into $result
447
     *
448
     * @param array         $result
449
     * @param string        $className
450
     * @param bool          $withEntityDetails
451
     * @param bool          $applyExclusions
452
     * @param bool          $translate
453
     */
454
    protected function addUnidirectionalRelations(
455
        array &$result,
456
        $className,
457
        $withEntityDetails,
458
        $applyExclusions,
459
        $translate
460
    ) {
461
        $relations = $this->getUnidirectionalRelations($className);
462
        foreach ($relations as $name => $mapping) {
463
            $relatedClassName = $mapping['sourceEntity'];
464
            $fieldName        = $mapping['fieldName'];
465
466
            if (!$this->isFieldAccessible($relatedClassName, $fieldName)) {
467
                continue;
468
            }
469
            $metadata = $this->getMetadataFor($relatedClassName);
470
            if ($this->isIgnoredRelation($metadata, $fieldName)) {
471
                continue;
472
            }
473
            if ($applyExclusions && $this->exclusionProvider->isIgnoredRelation($metadata, $fieldName)) {
0 ignored issues
show
Compatibility introduced by
$metadata of type object<Doctrine\Common\P...\Mapping\ClassMetadata> is not a sub-type of object<Doctrine\ORM\Mapping\ClassMetadata>. It seems like you assume a concrete implementation of the interface Doctrine\Common\Persistence\Mapping\ClassMetadata to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
474
                continue;
475
            }
476
477
            $labelKey     = $this->entityConfigProvider->getConfig($relatedClassName, $fieldName)->get('label');
478
            $labelType    = ($mapping['type'] & ClassMetadataInfo::TO_ONE) ? 'label' : 'plural_label';
479
            $labelTypeKey = $this->entityConfigProvider->getConfig($relatedClassName)->get($labelType);
480
            if ($translate) {
481
                $labelKey = $this->translator->trans($labelKey);
482
                $labelTypeKey = $this->translator->trans($labelTypeKey);
483
            }
484
            $label = sprintf('%s (%s)', $labelKey, $labelTypeKey);
485
486
            $fieldType = $this->getRelationFieldType($relatedClassName, $fieldName);
487
488
            $this->addRelation(
489
                $result,
490
                $name,
491
                $fieldType,
492
                $label,
493
                $this->getRelationType($fieldType),
494
                $relatedClassName,
495
                $withEntityDetails,
496
                $translate,
497
                false
498
            );
499
        }
500
    }
501
502
    /**
503
     * Return mapping data for entities that has one-way link to $className entity
504
     *
505
     * @param string $className
506
     *
507
     * @return array
508
     */
509
    protected function getUnidirectionalRelations($className)
510
    {
511
        $relations = [];
512
513
        $entityConfigs = $this->extendConfigProvider->getConfigs();
514
        foreach ($entityConfigs as $entityConfig) {
515
            if (!ExtendHelper::isEntityAccessible($entityConfig)) {
516
                continue;
517
            }
518
            $metadata = $this->getMetadataFor($entityConfig->getId()->getClassName());
519
            if ($this->isIgnoredEntity($metadata)) {
520
                continue;
521
            }
522
            $targetMappings = $metadata->getAssociationMappings();
523
            if (empty($targetMappings)) {
524
                continue;
525
            }
526
527
            foreach ($targetMappings as $mapping) {
528
                if ($mapping['isOwningSide']
529
                    && empty($mapping['inversedBy'])
530
                    && $mapping['targetEntity'] === $className
531
                ) {
532
                    $relations[$mapping['sourceEntity'] . '::' . $mapping['fieldName']] = $mapping;
533
                }
534
            }
535
        }
536
537
        return $relations;
538
    }
539
540
    /**
541
     * Adds a relation to $result
542
     *
543
     * @param array     $result
544
     * @param string    $name
545
     * @param string    $type
546
     * @param string    $label
547
     * @param string    $relationType
548
     * @param string    $relatedEntityName
549
     * @param bool      $withEntityDetails
550
     * @param bool      $translate
551
     * @param bool|null $translateLabel
552
     */
553
    protected function addRelation(
554
        array &$result,
555
        $name,
556
        $type,
557
        $label,
558
        $relationType,
559
        $relatedEntityName,
560
        $withEntityDetails,
561
        $translate,
562
        $translateLabel = null
563
    ) {
564
        if ($translateLabel === null) {
565
            $translateLabel = $translate;
566
        }
567
        if ($translateLabel) {
568
            $label = $this->translator->trans($label);
569
        }
570
571
        $relation = [
572
            'name'                => $name,
573
            'type'                => $type,
574
            'label'               => $label,
575
            'relation_type'       => $relationType,
576
            'related_entity_name' => $relatedEntityName
577
        ];
578
579
        if ($withEntityDetails) {
580
            $this->addEntityDetails($relatedEntityName, $relation, $translate);
581
        }
582
583
        $result[$name] = $relation;
584
    }
585
586
    /**
587
     * @param string $relatedEntityName
588
     * @param array  $relation
589
     * @param bool   $translate
590
     *
591
     * @return array
592
     */
593
    protected function addEntityDetails($relatedEntityName, array &$relation, $translate)
594
    {
595
        $entity = $this->entityProvider->getEntity($relatedEntityName, $translate);
596
        foreach ($entity as $key => $val) {
597
            if ($key !== 'name') {
598
                $relation['related_entity_' . $key] = $val;
599
            }
600
        }
601
602
        return $relation;
603
    }
604
605
    /**
606
     * Check if the given entity is ready to be used in a business logic.
607
     *
608
     * @param string $className
609
     *
610
     * @return bool
611
     */
612
    protected function isEntityAccessible($className)
613
    {
614
        return
615
            $this->extendConfigProvider->hasConfig($className)
616
            && ExtendHelper::isEntityAccessible($this->extendConfigProvider->getConfig($className));
617
    }
618
619
    /**
620
     * Check if the given field is ready to be used in a business logic.
621
     *
622
     * @param string $className
623
     * @param string $fieldName
624
     *
625
     * @return bool
626
     */
627
    protected function isFieldAccessible($className, $fieldName)
628
    {
629
        return
630
            $this->extendConfigProvider->hasConfig($className, $fieldName)
631
            && ExtendHelper::isFieldAccessible($this->extendConfigProvider->getConfig($className, $fieldName));
632
    }
633
634
    /**
635
     * Check if the given entity should be ignored
636
     *
637
     * @param ClassMetadataInterface $metadata
638
     *
639
     * @return bool
640
     */
641
    protected function isIgnoredEntity(ClassMetadataInterface $metadata)
0 ignored issues
show
Unused Code introduced by
The parameter $metadata is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
642
    {
643
        return false;
644
    }
645
646
    /**
647
     * Checks if the given field should be ignored
648
     *
649
     * @param ClassMetadataInterface $metadata
650
     * @param string                 $fieldName
651
     *
652
     * @return bool
653
     */
654
    protected function isIgnoredField(ClassMetadataInterface $metadata, $fieldName)
655
    {
656
        // @todo: use of $this->hiddenFields is a temporary solution (https://magecore.atlassian.net/browse/BAP-4142)
657
        if (isset($this->hiddenFields[$metadata->getName()][$fieldName])) {
658
            return true;
659
        }
660
661
        return false;
662
    }
663
664
    /**
665
     * Checks if the given relation should be ignored
666
     *
667
     * @param ClassMetadataInterface $metadata
668
     * @param string                 $associationName
669
     *
670
     * @return bool
671
     */
672
    protected function isIgnoredRelation(ClassMetadataInterface $metadata, $associationName)
673
    {
674
        return false;
675
    }
676
677
    /**
678
     * Gets doctrine entity manager for the given class
679
     *
680
     * @param string $className
681
     *
682
     * @return EntityManager
683
     * @throws InvalidEntityException
684
     */
685
    protected function getManagerForClass($className)
686
    {
687
        $manager = null;
688
        try {
689
            $manager = $this->doctrine->getManagerForClass($className);
690
        } catch (\ReflectionException $ex) {
691
            throw new InvalidEntityException(sprintf('The "%s" entity was not found.', $className));
692
        }
693
694
        return $manager;
695
    }
696
697
    /**
698
     * @param string $className
699
     *
700
     * @return ClassMetadataInterface|ClassMetadataInfo|ClassMetadata
701
     */
702
    protected function getMetadataFor($className)
703
    {
704
        return $this->getManagerForClass($className)->getMetadataFactory()->getMetadataFor($className);
705
    }
706
707
    /**
708
     * Gets a label of a field
709
     *
710
     * @param ClassMetadata $metadata
711
     * @param string        $fieldName
712
     *
713
     * @return string
714
     */
715
    protected function getFieldLabel(ClassMetadata $metadata, $fieldName)
716
    {
717
        $className = $metadata->getName();
718
        if (!$metadata->hasField($fieldName) && !$metadata->hasAssociation($fieldName)) {
719
            // virtual field or relation
720
            return ConfigHelper::getTranslationKey('entity', 'label', $className, $fieldName);
721
        }
722
723
        $label = $this->entityConfigProvider->hasConfig($className, $fieldName)
724
            ? $this->entityConfigProvider->getConfig($className, $fieldName)->get('label')
725
            : null;
726
727
        return !empty($label)
728
            ? $label
729
            : ConfigHelper::getTranslationKey('entity', 'label', $className, $fieldName);
730
    }
731
732
    /**
733
     * Gets a relation type
734
     *
735
     * @param string $className
736
     * @param string $fieldName
737
     *
738
     * @return string
739
     */
740
    protected function getRelationFieldType($className, $fieldName)
741
    {
742
        /** @var FieldConfigId $configId */
743
        $configId = $this->entityConfigProvider->getConfig($className, $fieldName)->getId();
744
745
        return $configId->getFieldType();
746
    }
747
748
    /**
749
     * Gets a relation type
750
     *
751
     * @param string $relationFieldType
752
     *
753
     * @return string
754
     */
755
    protected function getRelationType($relationFieldType)
756
    {
757
        return $this->fieldTypeHelper->getUnderlyingType($relationFieldType);
758
    }
759
760
    /**
761
     * Sorts fields by its label (relations follows fields)
762
     *
763
     * @param array $fields
764
     */
765
    protected function sortFields(array &$fields)
766
    {
767
        usort(
768
            $fields,
769
            function ($a, $b) {
770
                if (isset($a['relation_type']) !== isset($b['relation_type'])) {
771
                    if (isset($a['relation_type'])) {
772
                        return 1;
773
                    }
774
                    if (isset($b['relation_type'])) {
775
                        return -1;
776
                    }
777
                }
778
779
                return strcasecmp($a['label'], $b['label']);
780
            }
781
        );
782
    }
783
}
784