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

Bundle/ImportExportBundle/Field/FieldHelper.php (3 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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
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
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