Completed
Push — 1.9 ( 1529fd...66be26 )
by
unknown
43:32
created

AbstractMagentoConnector::getMaxUpdatedDate()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 14
rs 8.8571
c 1
b 0
f 0
cc 5
eloc 10
nc 8
nop 2
1
<?php
2
3
namespace OroCRM\Bundle\MagentoBundle\Provider;
4
5
use Doctrine\Common\Persistence\ManagerRegistry;
6
7
use Psr\Log\LoggerAwareInterface;
8
9
use Oro\Bundle\ImportExportBundle\Context\ContextRegistry;
10
use Oro\Bundle\ImportExportBundle\Context\ContextInterface;
11
use Oro\Bundle\IntegrationBundle\Entity\Channel as Integration;
12
use Oro\Bundle\IntegrationBundle\Entity\Status;
13
use Oro\Bundle\IntegrationBundle\Exception\RuntimeException;
14
use Oro\Bundle\IntegrationBundle\Logger\LoggerStrategy;
15
use Oro\Bundle\IntegrationBundle\Provider\AbstractConnector;
16
use Oro\Bundle\IntegrationBundle\Provider\ConnectorContextMediator;
17
18
use OroCRM\Bundle\MagentoBundle\Provider\Iterator\PredefinedFiltersAwareInterface;
19
use OroCRM\Bundle\MagentoBundle\Provider\Iterator\UpdatedLoaderInterface;
20
use OroCRM\Bundle\MagentoBundle\Provider\Transport\MagentoTransportInterface;
21
22
abstract class AbstractMagentoConnector extends AbstractConnector implements MagentoConnectorInterface
23
{
24
    const LAST_SYNC_KEY = 'lastSyncItemDate';
25
26
    /** @var MagentoTransportInterface */
27
    protected $transport;
28
29
    /** @var array */
30
    protected $bundleConfiguration;
31
32
    /** @var ManagerRegistry */
33
    protected $managerRegistry;
34
35
    /** @var string */
36
    protected $className;
37
38
    /**
39
     * @param ContextRegistry          $contextRegistry
40
     * @param LoggerStrategy           $logger
41
     * @param ConnectorContextMediator $contextMediator
42
     * @param array                    $bundleConfiguration
43
     */
44
    public function __construct(
45
        ContextRegistry $contextRegistry,
46
        LoggerStrategy $logger,
47
        ConnectorContextMediator $contextMediator,
48
        array $bundleConfiguration
49
    ) {
50
        parent::__construct($contextRegistry, $logger, $contextMediator);
51
        $this->bundleConfiguration = $bundleConfiguration;
52
    }
53
54
    /**
55
     * @param ManagerRegistry $managerRegistry
56
     */
57
    public function setManagerRegistry(ManagerRegistry $managerRegistry)
58
    {
59
        $this->managerRegistry = $managerRegistry;
60
    }
61
62
    /**
63
     * @param string $className
64
     */
65
    public function setClassName($className)
66
    {
67
        $this->className = $className;
68
    }
69
70
    /**
71
     * {@inheritdoc}
72
     */
73
    public function getImportEntityFQCN()
74
    {
75
        if (!$this->className) {
76
            throw new \InvalidArgumentException(sprintf('Entity FQCN is missing for "%s" connector', $this->getType()));
77
        }
78
79
        return $this->className;
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public function read()
86
    {
87
        $item = parent::read();
88
89
        if (null !== $item) {
90
            $this->addStatusData(
91
                self::LAST_SYNC_KEY,
92
                $this->getMaxUpdatedDate($this->getUpdatedDate($item), $this->getStatusData(self::LAST_SYNC_KEY))
93
            );
94
        }
95
        $iterator = $this->getSourceIterator();
96
        if (!$iterator->valid() && $iterator instanceof UpdatedLoaderInterface) {
97
            // cover case, when no one item was synced
98
            // then just take point from what it was started
99
            $dateFromReadStarted = $iterator->getStartDate() ? $iterator->getStartDate()->format('Y-m-d H:i:s') : null;
100
            $this->addStatusData(
101
                self::LAST_SYNC_KEY,
102
                $this->getMaxUpdatedDate($this->getStatusData(self::LAST_SYNC_KEY), $dateFromReadStarted)
103
            );
104
        }
105
106
        return $item;
107
    }
108
109
    /**
110
     * @param ContextInterface $context
111
     */
112
    protected function initializeTransport(ContextInterface $context)
113
    {
114
        $this->channel   = $this->contextMediator->getChannel($context);
115
        $this->transport = $this->contextMediator->getInitializedTransport($this->channel, true);
116
117
        $this->validateConfiguration();
118
        $this->setSourceIterator($this->getConnectorSource());
119
120
        $sourceIterator = $this->getSourceIterator();
121
        if ($sourceIterator instanceof LoggerAwareInterface) {
122
            $sourceIterator->setLogger($this->logger);
123
        }
124
    }
125
126
    /**
127
     * {@inheritdoc}
128
     */
129
    protected function initializeFromContext(ContextInterface $context)
130
    {
131
        $this->initializeTransport($context);
132
133
        // set start date and mode depending on status
134
        /** @var Status $status */
135
        $status = $this->getLastCompletedIntegrationStatus($this->channel, $this->getType());
136
        $iterator = $this->getSourceIterator();
137
138
        if ($iterator instanceof UpdatedLoaderInterface) {
139
            $startDate = $this->getStartDate($status);
140
141
            if ($status) {
142
                // use assumption interval in order to prevent mistiming issues
143
                $intervalString = $this->bundleConfiguration['sync_settings']['mistiming_assumption_interval'];
144
                $this->logger->debug(sprintf('Real start date: "%s"', $startDate->format(\DateTime::RSS)));
145
                $this->logger->debug(sprintf('Subtracted the presumable mistiming interval "%s"', $intervalString));
146
                $startDate->sub(\DateInterval::createFromDateString($intervalString));
147
            }
148
149
            $executionContext = $this->stepExecution->getJobExecution()->getExecutionContext();
150
            $interval = $executionContext->get(InitialSyncProcessor::INTERVAL);
151
            if ($interval) {
152
                $iterator->setMode(UpdatedLoaderInterface::IMPORT_MODE_INITIAL);
153
                $iterator->setSyncRange($interval);
154
            }
155
156
            $minimalSyncDate = $executionContext->get(InitialSyncProcessor::START_SYNC_DATE);
157
            if ($minimalSyncDate) {
158
                $iterator->setMinSyncDate($minimalSyncDate);
159
            }
160
161
            $iterator->setStartDate($startDate);
162
        }
163
164
        // pass filters from connector
165
        $this->setPredefinedFilters($context, $iterator);
166
    }
167
168
    /**
169
     * @param Status $status
170
     *
171
     * @return \DateTime
172
     */
173
    protected function getStartDate(Status $status = null)
174
    {
175
        $jobContext = $this->stepExecution->getJobExecution()->getExecutionContext();
176
        $initialSyncedTo = $jobContext->get(InitialSyncProcessor::INITIAL_SYNCED_TO);
177
        if ($initialSyncedTo) {
178
            return $initialSyncedTo;
179
        }
180
181
        // If connector has status use it's information for start date
182
        if ($status) {
183
            $data = $status->getData();
184
            if (empty($data[AbstractInitialProcessor::SKIP_STATUS])) {
185 View Code Duplication
                if (!empty($data[self::LAST_SYNC_KEY])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
186
                    return new \DateTime($data[self::LAST_SYNC_KEY], new \DateTimeZone('UTC'));
187
                }
188
189
                return clone $status->getDate();
190
            }
191
        }
192
193
        // If there is no status and LAST_SYNC_KEY is present in contexts use it
194
        $lastSyncDate = $this->stepExecution->getExecutionContext()->get(self::LAST_SYNC_KEY);
195
        if ($lastSyncDate) {
196
            return $lastSyncDate;
197
        } elseif ($jobContext->get(self::LAST_SYNC_KEY)) {
198
            return $jobContext->get(self::LAST_SYNC_KEY);
199
        }
200
201
        return new \DateTime('now', new \DateTimeZone('UTC'));
202
    }
203
204
    /**
205
     * Get last completed status for connector of integration instance.
206
     *
207
     * @param Integration $integration
208
     * @param string $connector
209
     * @return Status|null
210
     */
211
    protected function getLastCompletedIntegrationStatus(Integration $integration, $connector)
212
    {
213
        if (!$this->managerRegistry) {
214
            throw new RuntimeException('Doctrine manager registry is not initialized. Use setManagerRegistry method.');
215
        }
216
217
        return $this->managerRegistry->getRepository('OroIntegrationBundle:Channel')
218
            ->getLastStatusForConnector($integration, $connector, Status::STATUS_COMPLETED);
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224
    protected function validateConfiguration()
225
    {
226
        parent::validateConfiguration();
227
228
        if (!$this->transport instanceof MagentoTransportInterface) {
229
            throw new \LogicException('Option "transport" should implement "MagentoTransportInterface"');
230
        }
231
    }
232
233
    /**
234
     * @param string|null $currDateToCompare
235
     * @param string|null $prevDateToCompare
236
     *
237
     * @return null|string
238
     */
239
    protected function getMaxUpdatedDate($currDateToCompare, $prevDateToCompare)
240
    {
241
        if (!$prevDateToCompare) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $prevDateToCompare of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
242
            $date = $currDateToCompare;
243
        } elseif (!$currDateToCompare) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $currDateToCompare of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
244
            $date = $prevDateToCompare;
245
        } else {
246
            $date = strtotime($currDateToCompare) > strtotime($prevDateToCompare)
247
                ? $currDateToCompare
248
                : $prevDateToCompare;
249
        }
250
251
        return $date ? $this->getMinUpdatedDate($date) : $date;
252
    }
253
254
    /**
255
     * Compares maximum updated date with current date and returns the smallest.
256
     *
257
     * @param string $updatedDate
258
     *
259
     * @return string
260
     */
261
    protected function getMinUpdatedDate($updatedDate)
262
    {
263
        $currentDate = new \DateTime('now', new \DateTimeZone('UTC'));
264
        if ($currentDate->getTimestamp() > strtotime($updatedDate)) {
265
            return $updatedDate;
266
        }
267
268
        return $currentDate->format('Y-m-d H:i:s');
269
    }
270
271
    /**
272
     * @param array $item
273
     *
274
     * @return string|null
275
     */
276
    protected function getUpdatedDate(array $item)
277
    {
278
        switch (true) {
279
            case !empty($item['updatedAt']):
280
                return $item['updatedAt'];
281
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
282
            case !empty($item['updated_at']):
283
                return $item['updated_at'];
284
                break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
285
            default:
286
                return null;
287
        }
288
    }
289
290
    /**
291
     * @param ContextInterface $context
292
     * @param \Iterator $iterator
293
     */
294
    protected function setPredefinedFilters(ContextInterface $context, \Iterator $iterator)
295
    {
296
        $filters = null;
297
        if ($context->hasOption('filters')) {
298
            $filters = $context->getOption('filters');
299
            $context->removeOption('filters');
300
        }
301
302
        $complexFilters = null;
303
        if ($context->hasOption('complex_filters')) {
304
            $complexFilters = $context->getOption('complex_filters');
305
            $context->removeOption('complex_filters');
306
        }
307
308
        if ($filters || $complexFilters) {
309
            if ($iterator instanceof PredefinedFiltersAwareInterface) {
310
                $predefinedFilters = new BatchFilterBag((array)$filters, (array)$complexFilters);
311
312
                $iterator->setPredefinedFiltersBag($predefinedFilters);
313
            } else {
314
                throw new \LogicException('Iterator does not support predefined filters');
315
            }
316
        }
317
    }
318
}
319