Completed
Push — master ( 167ab4...693f6f )
by
unknown
12:29
created

TwoWaySyncStrategy::refreshNormalizedObject()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
dl 0
loc 10
rs 9.4285
c 1
b 1
f 0
cc 2
eloc 5
nc 2
nop 0
1
<?php
2
3
namespace OroCRM\Bundle\MagentoBundle\Provider\Strategy;
4
5
use Akeneo\Bundle\BatchBundle\Entity\StepExecution;
6
7
use Symfony\Component\PropertyAccess\PropertyAccess;
8
9
use Oro\Bundle\EntityBundle\ORM\DoctrineHelper;
10
use Oro\Bundle\IntegrationBundle\ImportExport\Processor\StepExecutionAwareExportProcessor;
11
use Oro\Bundle\IntegrationBundle\ImportExport\Processor\StepExecutionAwareImportProcessor;
12
use Oro\Bundle\IntegrationBundle\Provider\TwoWaySyncConnectorInterface;
13
14
class TwoWaySyncStrategy implements TwoWaySyncStrategyInterface
15
{
16
    /** @var array */
17
    protected $supportedStrategies = [
18
        TwoWaySyncConnectorInterface::REMOTE_WINS,
19
        TwoWaySyncConnectorInterface::LOCAL_WINS
20
    ];
21
22
    /** @var StepExecutionAwareImportProcessor */
23
    protected $importProcessor;
24
25
    /** @var StepExecutionAwareExportProcessor */
26
    protected $exportProcessor;
27
28
    /** @var DoctrineHelper */
29
    protected $doctrineHelper;
30
31
    /** @var object|null */
32
    protected $normalizedObject;
33
34
    /**
35
     * @param StepExecutionAwareImportProcessor $importProcessor
36
     * @param StepExecutionAwareExportProcessor $exportProcessor
37
     * @param DoctrineHelper $doctrineHelper
38
     */
39
    public function __construct(
40
        StepExecutionAwareImportProcessor $importProcessor,
41
        StepExecutionAwareExportProcessor $exportProcessor,
42
        DoctrineHelper $doctrineHelper
43
    ) {
44
        $this->importProcessor = $importProcessor;
45
        $this->exportProcessor = $exportProcessor;
46
        $this->doctrineHelper = $doctrineHelper;
47
    }
48
49
    /**
50
     * @param StepExecution $stepExecution
51
     */
52
    public function setStepExecution(StepExecution $stepExecution)
53
    {
54
        $this->importProcessor->setStepExecution($stepExecution);
55
        $this->exportProcessor->setStepExecution($stepExecution);
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function merge(
62
        array $changeSet,
63
        array $localData,
64
        array $remoteData,
65
        $strategy = TwoWaySyncConnectorInterface::REMOTE_WINS,
66
        array $additionalFields = []
67
    ) {
68
        if (!in_array($strategy, $this->supportedStrategies, true)) {
69
            throw new \InvalidArgumentException(
70
                sprintf(
71
                    'Strategy "%s" is not supported, expected one of "%s"',
72
                    $strategy,
73
                    implode(',', $this->supportedStrategies)
74
                )
75
            );
76
        }
77
78
        $remoteData = $this->normalize($remoteData);
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->normalize($remoteData); of type array|string adds the type string to the return on line 82 which is incompatible with the return type declared by the interface OroCRM\Bundle\MagentoBun...trategyInterface::merge of type array.
Loading history...
Bug Compatibility introduced by
The expression $this->normalize($remoteData); of type array|string adds the type string to the return on line 113 which is incompatible with the return type declared by the interface OroCRM\Bundle\MagentoBun...trategyInterface::merge of type array.
Loading history...
79
        if (!$changeSet) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $changeSet of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
80
            $this->refreshNormalizedObject();
81
82
            return $remoteData;
83
        }
84
85
        $oldValues = $this->getChangeSetValues($changeSet, 'old');
86
        $oldValues = $this->fillEmptyValues($oldValues, $this->getChangeSetValues($changeSet, 'new'));
87
        $snapshot = $this->getSnapshot($localData, $oldValues);
88
        $localChanges = $this->getDiff($localData, $snapshot);
0 ignored issues
show
Bug introduced by
It seems like $snapshot defined by $this->getSnapshot($localData, $oldValues) on line 87 can also be of type string; however, OroCRM\Bundle\MagentoBun...SyncStrategy::getDiff() does only seem to accept array, 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...
89
        $remoteChanges = $this->getDiff($remoteData, $snapshot);
0 ignored issues
show
Bug introduced by
It seems like $remoteData defined by $this->normalize($remoteData) on line 78 can also be of type string; however, OroCRM\Bundle\MagentoBun...SyncStrategy::getDiff() does only seem to accept array, 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...
Bug introduced by
It seems like $snapshot defined by $this->getSnapshot($localData, $oldValues) on line 87 can also be of type string; however, OroCRM\Bundle\MagentoBun...SyncStrategy::getDiff() does only seem to accept array, 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...
90
        $conflicts = array_keys(array_intersect_key($remoteChanges, $localChanges));
91
92
        foreach (array_merge($conflicts, $additionalFields) as $conflict) {
93
            if (!array_key_exists($conflict, $remoteData)) {
94
                continue;
95
            }
96
97
            if (!array_key_exists($conflict, $localData)) {
98
                continue;
99
            }
100
101
            if ($strategy === TwoWaySyncConnectorInterface::LOCAL_WINS) {
102
                $remoteData[$conflict] = $localData[$conflict];
103
            }
104
        }
105
106
        $localDataForUpdate = array_diff_key(array_keys($localChanges), $conflicts);
107
        foreach ($localDataForUpdate as $property) {
108
            $remoteData[$property] = $localData[$property];
109
        }
110
111
        $this->refreshNormalizedObject();
112
113
        return $remoteData;
114
    }
115
116
    /**
117
     * @param array $baseData
118
     * @param array $newData
119
     * @return array
120
     */
121
    protected function getDiff(array $baseData, array $newData)
122
    {
123
        $array = [];
124
125
        foreach ($baseData as $baseKey => $baseValue) {
126
            if (array_key_exists($baseKey, $newData)) {
127
                if (is_array($baseValue)) {
128
                    $diff = $this->getDiff($baseValue, $newData[$baseKey]);
129
                    if (count($diff)) {
130
                        $array[$baseKey] = $diff;
131
                    }
132
                } elseif ($baseValue != $newData[$baseKey]) {
133
                    $array[$baseKey] = $baseValue;
134
                }
135
            } else {
136
                $array[$baseKey] = $baseValue;
137
            }
138
        }
139
140
        return $array;
141
    }
142
143
    /**
144
     * @param array $localData
145
     * @param array $oldValues
146
     * @return array
147
     */
148
    protected function getSnapshot(array $localData, array $oldValues)
149
    {
150
        $object = $this->importProcessor->process($localData);
151
152
        $propertyAccessor = PropertyAccess::createPropertyAccessor();
153
154
        foreach ($oldValues as $propertyName => $value) {
155
            $propertyAccessor->setValue($object, $propertyName, $value);
156
        }
157
158
        return $this->exportProcessor->process($object);
159
    }
160
161
    /**
162
     * @param array $data
163
     * @return array
164
     */
165
    protected function normalize(array $data)
166
    {
167
        $this->normalizedObject = $this->importProcessor->process($data);
168
169
        return $this->exportProcessor->process($this->normalizedObject);
170
    }
171
172
    /**
173
     * Since object normalization does unwanted changes to the object,
174
     * it's necessary to revert it into original value
175
     *
176
     * This can be reproduced by refreshing page after changing magento customer address (e.g. first name)
177
     * with enabled 2 way sync.
178
     */
179
    protected function refreshNormalizedObject()
180
    {
181
        if (!$this->normalizedObject) {
182
            return;
183
        }
184
185
        $this->doctrineHelper->refreshIncludingUnitializedRelations($this->normalizedObject);
0 ignored issues
show
Bug introduced by
The method refreshIncludingUnitializedRelations() does not seem to exist on object<Oro\Bundle\Entity...dle\ORM\DoctrineHelper>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
186
187
        $this->normalizedObject = null;
188
    }
189
190
    /**
191
     * @param array $oldValues
192
     * @param array $newValues
193
     * @return array
194
     */
195
    protected function fillEmptyValues(array $oldValues, array $newValues)
196
    {
197
        $keysToCheck = array_keys($newValues);
198
        foreach ($keysToCheck as $key) {
199
            if (!array_key_exists($key, $oldValues)) {
200
                $oldValues[$key] = null;
201
            }
202
        }
203
204
        return $oldValues;
205
    }
206
207
    /**
208
     * @param array $changeSet
209
     * @param string $key
210
     * @return array
211
     */
212
    protected function getChangeSetValues($changeSet, $key)
213
    {
214
        $values = array_map(
215
            function ($data) use ($key) {
216
                if (empty($data[$key])) {
217
                    return null;
218
                }
219
220
                return $data[$key];
221
            },
222
            $changeSet
223
        );
224
225
        return array_filter($values);
226
    }
227
}
228