Completed
Push — master ( e32eeb...714cda )
by
unknown
89:50 queued 46:29
created

FieldHelper   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 389
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 7
Metric Value
wmc 50
lcom 2
cbo 7
dl 0
loc 389
rs 8.6206

20 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
B getFields() 0 25 2
A getRelations() 0 19 2
B getConfigValue() 0 23 4
A getCacheKey() 0 4 1
A hasConfig() 0 9 2
A isRelation() 0 4 2
A processRelationAsScalar() 0 4 1
A isSingleRelation() 0 9 2
A isMultipleRelation() 0 9 2
A isDateTimeField() 0 4 2
B getObjectValue() 0 18 5
A setObjectValue() 0 12 4
A setObjectValueWithReflection() 0 14 4
A getItemData() 0 12 4
A getIdentityValues() 0 7 1
A isRequiredIdentityField() 0 6 2
B getIdentityFieldNames() 0 18 5
A getFieldsValues() 0 9 2
A getPropertyAccessor() 0 8 2

How to fix   Complexity   

Complex Class

Complex classes like FieldHelper 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 FieldHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Oro\Bundle\ImportExportBundle\Field;
4
5
use Symfony\Component\PropertyAccess\PropertyAccess;
6
use Symfony\Component\PropertyAccess\PropertyAccessor;
7
use Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException;
8
9
use Doctrine\Common\Util\ClassUtils;
10
11
use Oro\Bundle\EntityBundle\Provider\EntityFieldProvider;
12
use Oro\Bundle\EntityConfigBundle\Provider\ConfigProvider;
13
use Oro\Bundle\EntityExtendBundle\Extend\FieldTypeHelper;
14
15
class FieldHelper
16
{
17
    const HAS_CONFIG = 'has_config';
18
19
    const IDENTITY_ONLY_WHEN_NOT_EMPTY = -1;
20
21
    /** @var ConfigProvider */
22
    protected $configProvider;
23
24
    /** @var EntityFieldProvider */
25
    protected $fieldProvider;
26
27
    /** @var FieldTypeHelper */
28
    protected $fieldTypeHelper;
29
30
    /** @var PropertyAccessor */
31
    protected $propertyAccessor;
32
33
    /** @var array */
34
    protected $fieldsCache = [];
35
36
    /** @var array */
37
    protected $relationsCache = [];
38
39
    /** @var array */
40
    protected $fieldsConfigCache = [];
41
42
    /** @var array */
43
    protected $identityFieldsCache = [];
44
45
    /**
46
     * @param EntityFieldProvider $fieldProvider
47
     * @param ConfigProvider      $configProvider
48
     * @param FieldTypeHelper     $fieldTypeHelper
49
     */
50
    public function __construct(
51
        EntityFieldProvider $fieldProvider,
52
        ConfigProvider $configProvider,
53
        FieldTypeHelper $fieldTypeHelper
54
    ) {
55
        $this->fieldProvider   = $fieldProvider;
56
        $this->configProvider  = $configProvider;
57
        $this->fieldTypeHelper = $fieldTypeHelper;
58
    }
59
60
    /**
61
     * @see \Oro\Bundle\EntityBundle\Provider\EntityFieldProvider::getFields
62
     *
63
     * @param string $entityName
64
     * @param bool   $withRelations
65
     * @param bool   $withVirtualFields
66
     * @param bool   $withEntityDetails
67
     * @param bool   $withUnidirectional
68
     * @param bool   $applyExclusions
69
     * @param bool   $translate
70
     * @return array
71
     */
72
    public function getFields(
73
        $entityName,
74
        $withRelations = false,
75
        $withVirtualFields = false,
76
        $withEntityDetails = false,
77
        $withUnidirectional = false,
78
        $applyExclusions = false,
79
        $translate = true
80
    ) {
81
        $args = func_get_args();
82
        $cacheKey = implode(':', $args);
83
        if (!array_key_exists($cacheKey, $this->fieldsCache)) {
84
            $this->fieldsCache[$cacheKey] = $this->fieldProvider->getFields(
85
                $entityName,
86
                $withRelations,
87
                $withVirtualFields,
88
                $withEntityDetails,
89
                $withUnidirectional,
90
                $applyExclusions,
91
                $translate
92
            );
93
        }
94
95
        return $this->fieldsCache[$cacheKey];
96
    }
97
98
    /**
99
     * @see \Oro\Bundle\EntityBundle\Provider\EntityFieldProvider::getRelations
100
     *
101
     * @param string $entityName
102
     * @param bool $withEntityDetails
103
     * @param bool $applyExclusions
104
     * @param bool $translate
105
     * @return array
106
     */
107
    public function getRelations(
108
        $entityName,
109
        $withEntityDetails = false,
110
        $applyExclusions = true,
111
        $translate = true
112
    ) {
113
        $args = func_get_args();
114
        $cacheKey = implode(':', $args);
115
        if (!array_key_exists($cacheKey, $this->relationsCache)) {
116
            $this->relationsCache[$cacheKey] = $this->fieldProvider->getRelations(
117
                $entityName,
118
                $withEntityDetails,
119
                $applyExclusions,
120
                $translate
121
            );
122
        }
123
124
        return $this->relationsCache[$cacheKey];
125
    }
126
127
    /**
128
     * @param string $entityName
129
     * @param string $fieldName
130
     * @param string $parameter
131
     * @param mixed  $default
132
     * @return mixed|null
133
     */
134
    public function getConfigValue($entityName, $fieldName, $parameter, $default = null)
135
    {
136
        $key = $this->getCacheKey($entityName, $fieldName);
137
138
        if (array_key_exists($key, $this->fieldsConfigCache)
139
            && array_key_exists($parameter, $this->fieldsConfigCache[$key])
140
        ) {
141
            return $this->fieldsConfigCache[$key][$parameter];
142
        }
143
144
        if (!$this->configProvider->hasConfig($entityName, $fieldName)) {
145
            $this->fieldsConfigCache[$key][self::HAS_CONFIG] = false;
146
            $this->fieldsConfigCache[$key][$parameter] = $default;
147
148
            return $this->fieldsConfigCache[$key][$parameter];
149
        }
150
151
        $this->fieldsConfigCache[$key][self::HAS_CONFIG] = true;
152
        $this->fieldsConfigCache[$key][$parameter] = $this->configProvider->getConfig($entityName, $fieldName)
153
            ->get($parameter, false, $default);
154
155
        return $this->fieldsConfigCache[$key][$parameter];
156
    }
157
158
    /**
159
     * @param string $entityName
160
     * @param string $fieldName
161
     * @return string
162
     */
163
    protected function getCacheKey($entityName, $fieldName)
164
    {
165
        return $entityName . ':' . $fieldName;
166
    }
167
168
    /**
169
     * @param string      $className
170
     * @param null|string $fieldName
171
     * @return bool
172
     */
173
    public function hasConfig($className, $fieldName = null)
174
    {
175
        $key = $this->getCacheKey($className, $fieldName);
176
        if (array_key_exists($key, $this->fieldsConfigCache)) {
177
            return !empty($this->fieldsConfigCache[$key][self::HAS_CONFIG]);
178
        }
179
180
        return $this->configProvider->hasConfig($className, $fieldName);
181
    }
182
183
    /**
184
     * @param array $field
185
     * @return bool
186
     */
187
    public function isRelation(array $field)
188
    {
189
        return !empty($field['relation_type']) && !empty($field['related_entity_name']);
190
    }
191
192
    /**
193
     * @param string $className
194
     * @param string $fieldName
195
     *
196
     * @return bool
197
     */
198
    public function processRelationAsScalar($className, $fieldName)
199
    {
200
        return (bool)$this->getConfigValue($className, $fieldName, 'process_as_scalar', false);
201
    }
202
203
    /**
204
     * @param array $field
205
     * @return bool
206
     */
207
    public function isSingleRelation(array $field)
208
    {
209
        return
210
            $this->isRelation($field)
211
            && in_array(
212
                $this->fieldTypeHelper->getUnderlyingType($field['relation_type']),
213
                ['ref-one', 'manyToOne']
214
            );
215
    }
216
217
    /**
218
     * @param array $field
219
     * @return bool
220
     */
221
    public function isMultipleRelation(array $field)
222
    {
223
        return
224
            $this->isRelation($field)
225
            && in_array(
226
                $this->fieldTypeHelper->getUnderlyingType($field['relation_type']),
227
                ['ref-many', 'oneToMany', 'manyToMany']
228
            );
229
    }
230
231
    /**
232
     * @param array $field
233
     * @return bool
234
     */
235
    public function isDateTimeField(array $field)
236
    {
237
        return !empty($field['type']) && in_array($field['type'], ['datetime', 'date', 'time']);
238
    }
239
240
    /**
241
     * @param object $object
242
     * @param string $fieldName
243
     * @return mixed
244
     * @throws \Exception
245
     */
246
    public function getObjectValue($object, $fieldName)
247
    {
248
        try {
249
            return $this->getPropertyAccessor()->getValue($object, $fieldName);
250
        } catch (\Exception $e) {
251
            $class = ClassUtils::getClass($object);
252
            while (!property_exists($class, $fieldName) && $class = get_parent_class($class)) {
253
            }
254
255
            if ($class) {
256
                $reflection = new \ReflectionProperty($class, $fieldName);
257
                $reflection->setAccessible(true);
258
                return $reflection->getValue($object);
259
            } else {
260
                throw $e;
261
            }
262
        }
263
    }
264
265
    /**
266
     * @param object $object
267
     * @param string $fieldName
268
     * @param mixed  $value
269
     * @throws NoSuchPropertyException|\TypeError|\ErrorException
270
     */
271
    public function setObjectValue($object, $fieldName, $value)
272
    {
273
        try {
274
            $this->getPropertyAccessor()->setValue($object, $fieldName, $value);
275
        } catch (NoSuchPropertyException $e) {
276
            $this->setObjectValueWithReflection($object, $fieldName, $value, $e);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type array; however, Oro\Bundle\ImportExportB...ctValueWithReflection() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
277
        } catch (\TypeError $e) {
278
            $this->setObjectValueWithReflection($object, $fieldName, $value, $e);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type array; however, Oro\Bundle\ImportExportB...ctValueWithReflection() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
279
        } catch (\ErrorException $e) {
280
            $this->setObjectValueWithReflection($object, $fieldName, $value, $e);
0 ignored issues
show
Bug introduced by
It seems like $object can also be of type array; however, Oro\Bundle\ImportExportB...ctValueWithReflection() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
281
        }
282
    }
283
284
    /**
285
     * If Property accessor have type_error
286
     * try added value by ReflectionProperty
287
     *
288
     * @param object $object
289
     * @param string $fieldName
290
     * @param mixed  $value
291
     * @param NoSuchPropertyException|\TypeError|\ErrorException $exception
292
     * @throws NoSuchPropertyException|\TypeError|\ErrorException
293
     */
294
    protected function setObjectValueWithReflection($object, $fieldName, $value, $exception)
295
    {
296
        $class = ClassUtils::getClass($object);
297
        while (!property_exists($class, $fieldName) && $class = get_parent_class($class)) {
298
        }
299
300
        if ($class) {
301
            $reflection = new \ReflectionProperty($class, $fieldName);
302
            $reflection->setAccessible(true);
303
            $reflection->setValue($object, $value);
304
        } else {
305
            throw $exception;
306
        }
307
    }
308
309
    /**
310
     * @param mixed $data
311
     * @param string $fieldName
312
     * @return array
313
     */
314
    public function getItemData($data, $fieldName = null)
315
    {
316
        if (!is_array($data)) {
317
            return [];
318
        }
319
320
        if (null === $fieldName) {
321
            return $data;
322
        }
323
324
        return !empty($data[$fieldName]) ? $data[$fieldName] : [];
325
    }
326
327
    /**
328
     * @param object $entity
329
     * @return array
330
     */
331
    public function getIdentityValues($entity)
332
    {
333
        $entityName = ClassUtils::getClass($entity);
334
        $identityFieldNames = $this->getIdentityFieldNames($entityName);
335
336
        return $this->getFieldsValues($entity, $identityFieldNames);
337
    }
338
339
    /**
340
     * Checks if a field should be used as an identity even if it has empty value
341
     *
342
     * @param string $entityName
343
     * @param string $fieldName
344
     *
345
     * @return bool
346
     */
347
    public function isRequiredIdentityField($entityName, $fieldName)
348
    {
349
        $value = $this->getConfigValue($entityName, $fieldName, 'identity', false);
350
351
        return $value && self::IDENTITY_ONLY_WHEN_NOT_EMPTY !== $value;
352
    }
353
354
    /**
355
     * @param string $entityName
356
     * @return string[]
357
     */
358
    public function getIdentityFieldNames($entityName)
359
    {
360
        if (!array_key_exists($entityName, $this->identityFieldsCache)) {
361
            $this->identityFieldsCache[$entityName] = [];
362
363
            $fields = $this->getFields($entityName, true);
364
            foreach ($fields as $field) {
365
                $fieldName = $field['name'];
366
                if (!$this->getConfigValue($entityName, $fieldName, 'excluded', false)
367
                    && $this->getConfigValue($entityName, $fieldName, 'identity', false)
368
                ) {
369
                    $this->identityFieldsCache[$entityName][] = $fieldName;
370
                }
371
            }
372
        }
373
374
        return $this->identityFieldsCache[$entityName];
375
    }
376
377
    /**
378
     * @param object $entity
379
     * @param array $fieldNames
380
     * @return array
381
     */
382
    public function getFieldsValues($entity, $fieldNames)
383
    {
384
        $values = [];
385
        foreach ($fieldNames as $fieldName) {
386
            $values[$fieldName] = $this->getObjectValue($entity, $fieldName);
387
        }
388
389
        return $values;
390
    }
391
392
    /**
393
     * @return PropertyAccessor
394
     */
395
    protected function getPropertyAccessor()
396
    {
397
        if (!$this->propertyAccessor) {
398
            $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
399
        }
400
401
        return $this->propertyAccessor;
402
    }
403
}
404