EntityMapper   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 317
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
wmc 45
eloc 117
dl 0
loc 317
ccs 133
cts 133
cp 1
rs 8.8
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A saveChildEntities() 0 18 3
A update() 0 12 2
A updateBitrixProperties() 0 19 4
A saveChildEntity() 0 22 5
B getChangedData() 0 15 8
A updateBitrixFields() 0 21 4
A add() 0 15 2
A select() 0 3 1
A checkChildEntity() 0 17 5
A getExistObjectRawResult() 0 23 3
A save() 0 18 3
A assert() 0 4 2
A entityToArray() 0 16 3

How to fix   Complexity   

Complex Class

Complex classes like EntityMapper 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.

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 EntityMapper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace BitrixToolkit\BitrixEntityMapper;
4
5
use CIBlockElement;
6
use DateTime;
7
use Doctrine\Common\Annotations\AnnotationException;
8
use Exception;
9
use InvalidArgumentException;
10
use ReflectionException;
11
use BitrixToolkit\BitrixEntityMapper\Annotation\Property\Field;
12
use BitrixToolkit\BitrixEntityMapper\Annotation\Property\Property;
13
use BitrixToolkit\BitrixEntityMapper\Map\EntityMap;
14
use BitrixToolkit\BitrixEntityMapper\Map\PropertyMap;
15
use BitrixToolkit\BitrixEntityMapper\Query\DataBuilder;
16
use BitrixToolkit\BitrixEntityMapper\Query\RawResult;
17
use BitrixToolkit\BitrixEntityMapper\Query\Select;
18
19
class EntityMapper
20
{
21
    /**
22
     * @param object $object
23
     * @return bool|int
24
     * @throws AnnotationException
25
     * @throws ReflectionException
26
     * @throws InvalidArgumentException
27
     * @throws Exception
28
     */
29 6
    public static function save($object)
30
    {
31 6
        self::assert(is_object($object), 'Аргумент $object не является объектом.');
32 5
        $class = get_class($object);
33 5
        $entityMap = EntityMap::fromClass($class);
34
35 5
        $data = self::entityToArray($entityMap, $object);
36
37
        // Сохраняем вложенные сущности
38 5
        $entityData = self::saveChildEntities($entityMap, $data);
39 5
        $data = array_replace($data, $entityData);
40
41 5
        $exist = self::getExistObjectRawResult($entityMap, $object);
42
43 5
        if ($exist && $exist->getId()) {
44 4
            return self::update($exist, $entityMap, $data);
45
        } else {
46 1
            return self::add($entityMap, $data);
47
        }
48
    }
49
50
    /**
51
     * @param string $class
52
     * @return Select
53
     * @throws AnnotationException
54
     * @throws ReflectionException
55
     * @throws InvalidArgumentException
56
     */
57 4
    public static function select($class)
58
    {
59 4
        return Select::from($class);
60
    }
61
62
    /**
63
     * @param EntityMap $entityMap
64
     * @param array $data
65
     * @return int
66
     * @throws Exception
67
     */
68 1
    protected static function add(EntityMap $entityMap, array $data)
69
    {
70 1
        $bitrixFields = DataBuilder::getBitrixFields($entityMap, $data);
71 1
        $bitrixProperties = DataBuilder::getBitrixProperties($entityMap, $data, $bitrixFields['IBLOCK_ID']);
72
73 1
        $addFields = $bitrixFields;
74 1
        if (!empty($bitrixProperties)) {
75 1
            $addFields['PROPERTY_VALUES'] = $bitrixProperties;
76
        }
77
78 1
        $cIBlockElement = new CIBlockElement();
79 1
        $elementId = $cIBlockElement->Add($addFields);
80 1
        self::assert($elementId, strip_tags($cIBlockElement->LAST_ERROR));
81
82 1
        return $elementId;
83
    }
84
85
    /**
86
     * @param RawResult $exist
87
     * @param EntityMap $entityMap
88
     * @param array $data
89
     * @return int
90
     * @throws Exception
91
     */
92 4
    protected static function update(RawResult $exist, EntityMap $entityMap, array $data)
93
    {
94 4
        $changedData = self::getChangedData($exist->getData(), $data);
95
96 4
        if (empty($changedData)) {
97 1
            return $exist->getId();
98
        }
99
100 3
        self::updateBitrixFields($exist, $entityMap, $changedData);
101 3
        self::updateBitrixProperties($exist, $entityMap, $changedData);
102
103 3
        return $exist->getId();
104
    }
105
106
    /**
107
     * @param array $exist
108
     * @param array $data
109
     * @return array
110
     */
111 4
    protected static function getChangedData(array $exist, array $data)
112
    {
113 4
        return array_udiff_assoc($data, $exist, function ($new, $old) {
114 4
            $normalize = function ($value) {
115 4
                if ($value instanceof DateTime) {
116 4
                    return $value->getTimestamp();
117
                }
118
119 4
                return $value;
120 4
            };
121
122 4
            $new = $new === null || $new === false || $new === [] ? false : array_map($normalize, (array)$new);
123 4
            $old = $old === null || $old === false || $old === [] ? false : array_map($normalize, (array)$old);
124
125 4
            return $new !== $old;
126 4
        });
127
    }
128
129
    /**
130
     * @param RawResult $exist
131
     * @param EntityMap $entityMap
132
     * @param array $changedData
133
     * @throws Exception
134
     */
135 3
    protected static function updateBitrixFields(RawResult $exist, EntityMap $entityMap, array $changedData)
136
    {
137 3
        $changedFields = array_filter($entityMap->getProperties(), function (PropertyMap $field) use ($changedData) {
138 3
            return (
139 3
                $field->getAnnotation() instanceof Field &&
140 3
                in_array($field->getReflection()->getName(), array_keys($changedData))
141 3
            );
142 3
        });
143
144 3
        if (empty($changedFields)) {
145 2
            return;
146
        }
147
148 1
        $bitrixFields = [];
149 1
        foreach ($changedFields as $changedField) {
150 1
            $bitrixFields += DataBuilder::getBitrixFieldEntry($changedField, $changedData);
151
        }
152
153 1
        $cIBlockElement = new CIBlockElement();
154 1
        $isUpdated = $cIBlockElement->Update($exist->getId(), $bitrixFields);
155 1
        self::assert($isUpdated, strip_tags($cIBlockElement->LAST_ERROR));
156
    }
157
158
    /**
159
     * @param RawResult $exist
160
     * @param EntityMap $entityMap
161
     * @param array $changedData
162
     * @throws Exception
163
     */
164 3
    protected static function updateBitrixProperties(RawResult $exist, EntityMap $entityMap, array $changedData)
165
    {
166 3
        $changedProperties = array_filter($entityMap->getProperties(), function (PropertyMap $property) use ($changedData) {
167 3
            return (
168 3
                $property->getAnnotation() instanceof Property &&
169 3
                in_array($property->getReflection()->getName(), array_keys($changedData))
170 3
            );
171 3
        });
172
173 3
        if (empty($changedProperties)) {
174 1
            return;
175
        }
176
177 3
        $bitrixProperties = [];
178 3
        foreach ($changedProperties as $changedProperty) {
179 3
            $bitrixProperties += DataBuilder::getBitrixPropertyEntry($changedProperty, $changedData, $exist->getInfoBlockId());
180
        }
181
182 3
        CIBlockElement::SetPropertyValuesEx($exist->getId(), $exist->getInfoBlockId(), $bitrixProperties);
183
    }
184
185
    /**
186
     * @param EntityMap $entityMap
187
     * @param array $data
188
     * @return array
189
     * @throws AnnotationException
190
     * @throws ReflectionException
191
     * @throws InvalidArgumentException
192
     */
193 5
    protected static function saveChildEntities(EntityMap $entityMap, array $data)
194
    {
195
        /** @var PropertyMap[] $entityProperties */
196 5
        $entityProperties = array_filter($entityMap->getProperties(), function (PropertyMap $propertyMap) {
197 5
            return $propertyMap->getAnnotation()->getType() === Property::TYPE_ENTITY;
198 5
        });
199
200 5
        foreach ($entityProperties as $entityProperty) {
201 5
            self::checkChildEntity($entityProperty, $data);
202
        }
203
204 5
        $entityData = [];
205 5
        foreach ($entityProperties as $entityProperty) {
206 5
            $entityPropertyData = self::saveChildEntity($entityProperty, $data);
207 5
            $entityData += $entityPropertyData;
208
        }
209
210 5
        return $entityData;
211
    }
212
213
    /**
214
     * @param PropertyMap $entityProperty
215
     * @param array $data
216
     * @return array
217
     * @throws AnnotationException
218
     * @throws ReflectionException
219
     */
220 5
    protected static function saveChildEntity(PropertyMap $entityProperty, array $data)
221
    {
222 5
        $entityData = [];
223 5
        $key = $entityProperty->getCode();
224
225 5
        $rawValue = array_key_exists($key, $data) ? $data[$key] : null;
226 5
        if (empty($rawValue)) {
227 4
            $entityData[$key] = false;
228 4
            return $entityData;
229
        }
230
231 2
        if ($entityProperty->getAnnotation()->isMultiple()) {
232 1
            foreach ($rawValue as $object) {
233 1
                $objectId = self::save($object);
234 1
                $entityData[$key][] = $objectId;
235
            }
236
        } else {
237 2
            $objectId = self::save($rawValue);
238 2
            $entityData[$key] = $objectId;
239
        }
240
241 2
        return $entityData;
242
    }
243
244
    /**
245
     * @param PropertyMap $entityProperty
246
     * @param array $data
247
     * @throws InvalidArgumentException
248
     */
249 5
    protected static function checkChildEntity(PropertyMap $entityProperty, array $data)
250
    {
251 5
        $key = $entityProperty->getCode();
252 5
        self::assert(array_key_exists($key, $data), "Ключ $key не найден в массиве данных полученных из объекта.");
253 5
        $value = $data[$key];
254
255 5
        if ($entityProperty->getAnnotation()->isMultiple()) {
256 5
            $objects = $value ? $value : [];
257 5
            self::assert(is_array($objects), 'Множественное значение должно быть массивом.');
258
        } else {
259 5
            $objects = $value ? [$value] : [];
260
        }
261
262 5
        $needClass = $entityProperty->getAnnotation()->getEntity();
263 5
        foreach ($objects as $object) {
264 2
            self::assert(is_object($object), 'Значение типа ' . Property::TYPE_ENTITY . ' должно быть объектом.');
265 2
            self::assert($object instanceof $needClass, "Объект должен быть экземпляром класса $needClass.");
266
        }
267
    }
268
269
    /**
270
     * @param mixed $term
271
     * @param string $msg
272
     * @throws InvalidArgumentException
273
     */
274 6
    protected static function assert($term, $msg)
275
    {
276 6
        if (!$term) {
277 1
            throw new InvalidArgumentException($msg);
278
        }
279
    }
280
281
    /**
282
     * @param EntityMap $entityMap
283
     * @param object $object
284
     * @return array
285
     */
286 5
    protected static function entityToArray(EntityMap $entityMap, $object)
287
    {
288 5
        $data = [];
289 5
        foreach ($entityMap->getProperties() as $propertyMap) {
290 5
            if (!$propertyMap->getReflection()->isPublic()) {
291 5
                $propertyMap->getReflection()->setAccessible(true);
292 5
                $value = $propertyMap->getReflection()->getValue($object);
293 5
                $propertyMap->getReflection()->setAccessible(false);
294
            } else {
295 5
                $value = $propertyMap->getReflection()->getValue($object);
296
            }
297
298 5
            $data[$propertyMap->getReflection()->getName()] = $value;
299
        }
300
301 5
        return $data;
302
    }
303
304
    /**
305
     * @param EntityMap $entityMap
306
     * @param object $object
307
     * @return RawResult|null
308
     * @throws AnnotationException
309
     * @throws ReflectionException
310
     * @throws InvalidArgumentException
311
     * @throws Exception
312
     */
313 5
    protected static function getExistObjectRawResult(EntityMap $entityMap, $object)
314
    {
315
        /** @var PropertyMap[] $primaryKeys */
316 5
        $primaryKeys = array_filter($entityMap->getProperties(), function (PropertyMap $propertyMap) {
317 5
            return $propertyMap->getAnnotation()->isPrimaryKey();
318 5
        });
319
320 5
        $data = self::entityToArray($entityMap, $object);
321
322 5
        $exist = null;
323 5
        if (!empty($primaryKeys)) {
324 5
            $select = Select::from($entityMap->getClass());
325 5
            foreach ($primaryKeys as $primaryKey) {
326 5
                $key = $primaryKey->getReflection()->getName();
327 5
                self::assert(array_key_exists($key, $data), "Ключ $key не найден в массиве данных полученных из объекта.");
328 5
                $select->where($key, $data[$key]);
329
            }
330
331
            /** @var RawResult $exist */
332 5
            $exist = $select->rawIterator()->current();
333
        }
334
335 5
        return $exist;
336
    }
337
}