getCurrentAppoiments()   A
last analyzed

Complexity

Conditions 3
Paths 4

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.9332
c 0
b 0
f 0
cc 3
nc 4
nop 3
1
<?php
2
3
namespace Wabel\CertainAPI\Services;
4
5
use Logger\Formatters\DateTimeFormatter;
6
use Mouf\Utils\Common\Lock;
7
use Mouf\Utils\Log\Psr\MultiLogger;
8
use Symfony\Component\Console\Logger\ConsoleLogger;
9
use Symfony\Component\Console\Output\OutputInterface;
10
use Wabel\CertainAPI\Helpers\FileChangesHelper;
11
use Wabel\CertainAPI\Interfaces\CertainListener;
12
use Wabel\CertainAPI\Ressources\AppointmentsCertain;
13
14
class DetectAppointmentsChangingsService
15
{
16
17
    /**
18
     * @var AppointmentsCertain
19
     */
20
    private $appointmentsCertain;
21
    /**
22
     * @var MultiLogger
23
     */
24
    private $logger;
25
    /**
26
     * @var Lock
27
     */
28
    private $lock;
29
    /**
30
     * @var string
31
     */
32
    private $dirPathHistoryAppointments;
33
    /**
34
     * @var CertainListener[]
35
     */
36
    private $listeners;
37
38
    /**
39
     * @param CertainListener[] $listeners
40
     */
41
    public function __construct(AppointmentsCertain $appointmentsCertain, MultiLogger $logger, Lock $lock, string $dirPathHistoryAppointments, array $listeners = [])
42
    {
43
        $this->appointmentsCertain = $appointmentsCertain;
44
        $this->logger = $logger;
45
        $this->lock = $lock;
46
        $this->dirPathHistoryAppointments = $dirPathHistoryAppointments;
47
        $this->listeners = $listeners;
48
    }
49
50
    /**
51
     * @param $eventCode
52
     * @param null|int $start
53
     * @param null|int $maxResult
54
     * @return mixed
55
     */
56
    public function getCurrentAppoiments($eventCode, $start = null, $maxResult = null)
57
    {
58
        if (!$start) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $start of type null|integer is loosely compared to false; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
59
            $start = 0;
60
        }
61
        if (!$maxResult) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $maxResult of type null|integer is loosely compared to false; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
62
            $maxResult = 999999;
63
        }
64
        return $this->certainAppointmentsList = $this->appointmentsCertain->get($eventCode, ['start_index' => $start, 'max_results' => $maxResult])->getResults()->appointments;
0 ignored issues
show
Bug introduced by
The property certainAppointmentsList does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
65
    }
66
67
    /**
68
     * @param array $appointmentsOld
69
     * @param array $appointmentsNew
70
     * @return bool
71
     */
72
    public function hasChanged(array $appointmentsOld, array $appointmentsNew)
73
    {
74
        $hasChanged = false;
75
        $appointmentsOld = self::recursiveArrayObjectToFullArray($appointmentsOld);
76
        $appointmentsNew = self::recursiveArrayObjectToFullArray($appointmentsNew);
77
        //Has change by update or delete
78
        foreach ($appointmentsOld as $appointmentOld) {
79
            if (!in_array($appointmentOld, $appointmentsNew)) {
80
                $hasChanged = true;
81
                break;
82
            }
83
        }
84
        //Has changes by insertion or update
85
        if (!$hasChanged) {
86
            foreach ($appointmentsNew as $appointmentNew) {
87
                if (!in_array($appointmentNew, $appointmentsOld)) {
88
                    $hasChanged = true;
89
                    break;
90
                }
91
            }
92
        }
93
        return $hasChanged;
94
    }
95
96
    /**
97
     * @param $object
98
     * @return array
99
     */
100
    public static function objectToArray($object)
101
    {
102
        if (is_object($object)) {
103
104
            return (array)$object;
105
        }
106
        return $object;
107
    }
108
109
    /**
110
     * @param $appointments
111
     * @return array
112
     */
113
    public static function recursiveArrayObjectToFullArray($appointments)
114
    {
115
        return json_decode(json_encode($appointments), true);
116
    }
117
118
    /**
119
     * @param array $arrayOlds
120
     * @param array $arrayNews
121
     * @return array
122
     */
123
    private function arrayRecursiveDiff(array $arrayOlds, array $arrayNews)
124
    {
125
        $difference = [];
126
        foreach ($arrayOlds as $key => $arrayOld) {
127
            if (!in_array($arrayOld, $arrayNews)) {
128
                $difference[$key] = $arrayOld;
129
            }
130
        }
131
        return $difference;
132
    }
133
134
    /**
135
     * @param array $arrayOlds
136
     * @param array $arrayNews
137
     * @return array
138
     */
139
    private function arrayRecursiveDiffNew(array $arrayOlds, array $arrayNews)
140
    {
141
        $difference = [];
142
        $appointmentIdOlds = array_map(function($appointment) {
143
            return $appointment['appointmentId'];
144
        }, $arrayOlds);
145
        foreach ($arrayNews as $key => $arrayNew) {
146
            if (!in_array($arrayNew['appointmentId'], $appointmentIdOlds)) {
147
                $difference[$key] = $arrayNew;
148
            }
149
        }
150
        return $difference;
151
    }
152
153
    /**
154
     * @param array $appointmentsOld
155
     * @param array $appointmentsNew
156
     * @return array
157
     */
158
    public function getListChangings(array $appointmentsOld, array $appointmentsNew)
159
    {
160
        $appointmentsOld = self::recursiveArrayObjectToFullArray($appointmentsOld);
161
        $appointmentsNew = self::recursiveArrayObjectToFullArray($appointmentsNew);
162
        $changesListUpdateOrDelete = [];
163
        $changesListInsert = [];
164
        if ($this->hasChanged($appointmentsOld, $appointmentsNew)) {
165
            $changesListUpdateOrDelete = self::recursiveArrayObjectToFullArray($this->arrayRecursiveDiff($appointmentsOld, $appointmentsNew));
166
            $changesListInsert = self::recursiveArrayObjectToFullArray($this->arrayRecursiveDiffNew($appointmentsOld, $appointmentsNew));
167
        }
168
        return [
169
            'inserted' => $changesListInsert,
170
            'updated_deleted' => $changesListUpdateOrDelete
171
        ];
172
    }
173
174
    /**
175
     *
176
     * @param array $currentAppointments
177
     * @param array $changingsDetected
178
     * @return array ['deleted'=>[],'updated'=>[]]
179
     */
180
    public function detectDeleteOrUpdated(array $currentAppointments, array $changingsDetected)
181
    {
182
        $delete = [];
183
        $update = [];
184
        //@Todo: Detect Fields has changed
185
        $appointmentsNew = self::recursiveArrayObjectToFullArray($currentAppointments);
186
        $changings = self::recursiveArrayObjectToFullArray($changingsDetected);
187
        foreach ($changings as $changing) {
188
            $registration = $changing['registration']['regCode'];
189
            $registrationTarget = $changing['targetRegistration']['regCode'];
190
            $runInNew = 0;
191
            foreach ($appointmentsNew as $currentAppointment) {
192
                $registrationCurrent = $currentAppointment['registration']['regCode'];
193
                $registrationTargetCurrent = $currentAppointment['targetRegistration']['regCode'];
194
                if (in_array($registration, [$registrationCurrent, $registrationTargetCurrent])
195
                    && in_array($registrationTarget, [$registrationCurrent, $registrationTargetCurrent])
196
                    && !in_array($changing, $update) && !in_array($changing, $delete)) {
197
                    $update[] = $currentAppointment;
198
                    break;
199
                }
200
                $runInNew++;
201
            }
202
            if ($runInNew === count($appointmentsNew) && !in_array($changing, $delete)) {
203
                $delete[] = $changing;
204
            }
205
206
        }
207
        return [
208
            'deleted' => $delete,
209
            'updated' => $update,
210
        ];
211
    }
212
213
    /**
214
     * @param array $appointments
215
     * @param $timestamp
216
     * @return array
217
     */
218
    public static function insertDateTimeChanges(array $appointments, $timestamp)
219
    {
220
        foreach ($appointments as $key => $appointment) {
221
            $appointments[$key]['dateDetectChanges'] = $timestamp;
222
        }
223
        return $appointments;
224
    }
225
226
    /**
227
     * @param array $appointmentsOld
228
     * @param array $appointmentsNew
229
     * @param string $timestamp
230
     * @return array ['deleted'=>[],'updated'=>[]]
231
     */
232
    public function detectAppointmentsChangings(array $appointmentsOld, array $appointmentsNew, $timestamp)
233
    {
234
        $changings = $this->getListChangings($appointmentsOld, $appointmentsNew);
235
        $changesList = $this->detectDeleteOrUpdated($appointmentsNew, $changings['updated_deleted']);
236
        $changesList['inserted'] = self::insertDateTimeChanges($changings['inserted'], $timestamp);
237
        $changesList['updated'] = self::insertDateTimeChanges($changesList['updated'], $timestamp);
238
        $changesList['deleted'] = self::insertDateTimeChanges($changesList['deleted'], $timestamp);
239
        return $changesList;
240
    }
241
242
    public function runCommandForEvent(string $eventCode, OutputInterface $output = null): void
243
    {
244
        if ($output) {
245
            $this->logger->addLogger(new DateTimeFormatter(new ConsoleLogger($output)));
246
        }
247
        $codeCheckDirectory = FileChangesHelper::checkDirectory($this->dirPathHistoryAppointments);
248
        if ($codeCheckDirectory === 'no_directory') {
249
            $this->logger->error('Path ' . $this->dirPathHistoryAppointments . ' doesn\'t exists.');
250
            return;
251
        }
252
        if ($codeCheckDirectory === 'not_readable') {
253
            $this->logger->error('Path ' . $this->dirPathHistoryAppointments . ' is not readable.');
254
            return;
255
        }
256
        if ($codeCheckDirectory === 'not_writable') {
257
            $this->logger->error('Path ' . $this->dirPathHistoryAppointments . ' is not writable.');
258
            return;
259
        }
260
        $this->lock->acquireLock();
261
        $this->logger->info('Detect changes - Run.');
262
        //That permits to stop the followings instructions when we are makings changes on Certain.
263
        //Get the online appointments.
264
        $appointmentsNewCertain = $this->getCurrentAppoiments($eventCode);
265
        $appointmentsNew = self::recursiveArrayObjectToFullArray($appointmentsNewCertain);
266
        //Get the last saved appointments to get old data.
267
        $appointmentsOldHistoryFilePath = FileChangesHelper::getTheLastAppointmentsSaved($eventCode, $this->dirPathHistoryAppointments);
268
        if (!$appointmentsOldHistoryFilePath) {
269
            //No files so it's the first time we attempt to synchronize.
270
            $appointmentsOld = [];
271
        } else {
272
            //Get the last old appointments data.
273
            $appointmentsOldHistory = FileChangesHelper::getJsonContentFromFile($appointmentsOldHistoryFilePath);
274
            $appointmentsOld = self::recursiveArrayObjectToFullArray($appointmentsOldHistory);
275
        }
276
        //Check if they are changes.
277
        $timestamp = time();
278
//        $listChangings = $this->detectAppointmentsChangings($appointmentsOld, $appointmentsNew, $timestamp);
279
        $listChangings = $this->detectAppointmentsChanges($appointmentsOld, $appointmentsNew, $timestamp);
280
        if (!$appointmentsOld || ((isset($listChangings['updated']) && !empty($listChangings['updated']))
0 ignored issues
show
Bug Best Practice introduced by
The expression $appointmentsOld 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...
281
                || (isset($listChangings['deleted']) && !empty($listChangings['deleted']))  || (isset($listChangings['inserted']) && !empty($listChangings['inserted'])))) {
282
            //Changes? So we save the new online appointments
283
            FileChangesHelper::saveAppointmentsFileByHistory($this->dirPathHistoryAppointments . '/appointments_' . $eventCode . '.json', json_encode($appointmentsNew));
284
            $this->logger->info('Detect changes - Save Changes');
285
        } else {
286
            $this->logger->info('Detect changes - No Changes');
287
        }
288
        FileChangesHelper::saveAppointmentsFileByHistory($this->dirPathHistoryAppointments . '/changes_' . $eventCode . '.json', json_encode($listChangings));
289
        foreach ($this->listeners as $listener) {
290
            //Run Listener. For instance,Here we can use ChangingsToFileListeners to save the changes in file.
291
            $listener->run($eventCode, $listChangings);
292
        }
293
        $this->logger->info('Detect changes - Stop.');
294
        $this->lock->releaseLock();
295
    }
296
297
    private function detectAppointmentsChanges(array $appointmentsOld, array $appointmentsNew, int $timestamp)
298
    {
299
        $oldAppointments = [];
300
        $newAppointments = [];
301
        $oldAppointmentIds = [];
302
        $newAppointmentIds = [];
303
304
        foreach ($appointmentsOld as $item) {
305
            $oldAppointments[$item['appointmentId']] = $item;
306
            $oldAppointmentIds[] = $item['appointmentId'];
307
        }
308
        foreach ($appointmentsNew as $item) {
309
            $newAppointments[$item['appointmentId']] = $item;
310
            $newAppointmentIds[] = $item['appointmentId'];
311
        }
312
313
        $data = [
314
            'inserted' => [],
315
            'updated' => [],
316
            'deleted' => [],
317
        ];
318
319
        $data['inserted'] = array_map(static function($item) use ($newAppointments) {
320
            return $newAppointments[$item];
321
        }, array_diff($newAppointmentIds, $oldAppointmentIds));
322
323
        $data['deleted'] = array_map(static function($item) use ($oldAppointments) {
324
            return $oldAppointments[$item];
325
        }, array_diff($oldAppointmentIds, $newAppointmentIds));
326
327
        $updatedIds = array_keys(array_intersect_key($newAppointments, $oldAppointments));
328
        foreach ($updatedIds as $updatedId) {
329
            // Important we use simple != and not strict !== to avoid issue if positions in array are different
330
            if ($newAppointments[$updatedId] != $oldAppointments[$updatedId]) {
331
                $data['updated'][] = $newAppointments[$updatedId];
332
            }
333
        }
334
335
        $data['inserted'] = self::insertDateTimeChanges($data['inserted'], $timestamp);
336
        $data['updated'] = self::insertDateTimeChanges($data['updated'], $timestamp);
337
        $data['deleted'] = self::insertDateTimeChanges($data['deleted'], $timestamp);
338
339
        return $data;
340
    }
341
342
}
343