Completed
Push — master ( b1d34e...57baf4 )
by Raphaël
04:46 queued 02:06
created

detectAppointmentsChangings()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
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
     * @param array $existedUpdateOrDelete
138
     * @return array
139
     */
140
    private function arrayRecursiveDiffNew(array $arrayOlds, array $arrayNews, array $existedUpdateOrDelete)
141
    {
142
        $difference = [];
143
        foreach ($arrayNews as $key => $arrayNew) {
144
            if (!in_array($arrayNew, $arrayOlds)
145
                && !in_array(self::recursiveArrayObjectToFullArray($arrayNew), $existedUpdateOrDelete)) {
146
                $difference[$key] = $arrayNew;
147
            }
148
        }
149
        return $difference;
150
    }
151
152
    /**
153
     * @param array $appointmentsOld
154
     * @param array $appointmentsNew
155
     * @return array
156
     */
157
    public function getListChangings(array $appointmentsOld, array $appointmentsNew)
158
    {
159
        $appointmentsOld = self::recursiveArrayObjectToFullArray($appointmentsOld);
160
        $appointmentsNew = self::recursiveArrayObjectToFullArray($appointmentsNew);
161
        $changesListUpdateOrDelete = [];
162
        $changesListInsert = [];
163
        if ($this->hasChanged($appointmentsOld, $appointmentsNew)) {
164
            $changesListUpdateOrDelete = self::recursiveArrayObjectToFullArray($this->arrayRecursiveDiff($appointmentsOld, $appointmentsNew));
165
            $changesListInsert = self::recursiveArrayObjectToFullArray($this->arrayRecursiveDiffNew($appointmentsOld, $appointmentsNew, $changesListUpdateOrDelete));
166
        }
167
        return [
168
            'inserted' => $changesListInsert,
169
            'updated_deleted' => $changesListUpdateOrDelete
170
        ];
171
    }
172
173
    /**
174
     *
175
     * @param array $currentAppointments
176
     * @param array $changingsDetected
177
     * @return array ['deleted'=>[],'updated'=>[]]
178
     */
179
    public function detectDeleteOrUpdated(array $currentAppointments, array $changingsDetected)
180
    {
181
        $delete = [];
182
        $update = [];
183
        //@Todo: Detect Fields has changed
184
        $appointmentsNew = self::recursiveArrayObjectToFullArray($currentAppointments);
185
        $changings = self::recursiveArrayObjectToFullArray($changingsDetected);
186
        foreach ($changings as $changing) {
187
            $registration = $changing['registration']['regCode'];
188
            $registrationTarget = $changing['targetRegistration']['regCode'];
189
            foreach ($appointmentsNew as $currentAppointment) {
190
                $registrationCurrent = $currentAppointment['registration']['regCode'];
191
                $registrationTargetCurrent = $currentAppointment['targetRegistration']['regCode'];
192
                if (in_array($registration, [$registrationCurrent, $registrationTargetCurrent])
193
                    && in_array($registrationTarget, [$registrationCurrent, $registrationTargetCurrent])
194
                    && !in_array($changing, $update) && !in_array($changing, $delete)) {
195
                    $update[] = $changing;
196
                    break;
197
                }
198
            }
199
            if (!in_array($changing, $update) && !in_array($changing, $delete)) {
200
                $delete[] = $changing;
201
            }
202
203
        }
204
        return [
205
            'deleted' => $delete,
206
            'updated' => $update,
207
        ];
208
    }
209
210
    /**
211
     * @param array $appointments
212
     * @param $timestamp
213
     * @return array
214
     */
215
    public static function insertDateTimeChanges(array $appointments, $timestamp)
216
    {
217
        foreach ($appointments as $key => $appointment) {
218
            $appointments[$key]['dateDetectChanges'] = $timestamp;
219
        }
220
        return $appointments;
221
    }
222
223
    /**
224
     * @param array $appointmentsOld
225
     * @param array $appointmentsNew
226
     * @param string $timestamp
227
     * @return array ['deleted'=>[],'updated'=>[]]
228
     */
229
    public function detectAppointmentsChangings(array $appointmentsOld, array $appointmentsNew, $timestamp)
230
    {
231
        $changings = $this->getListChangings($appointmentsOld, $appointmentsNew);
232
        $changesList = $this->detectDeleteOrUpdated($appointmentsNew, $changings['updated_deleted']);
233
        $changesList['inserted'] = self::insertDateTimeChanges($changings['inserted'], $timestamp);
234
        $changesList['updated'] = self::insertDateTimeChanges($changesList['updated'], $timestamp);
235
        $changesList['deleted'] = self::insertDateTimeChanges($changesList['deleted'], $timestamp);
236
        return $changesList;
237
    }
238
239
    public function runCommandForEvent(string $eventCode, OutputInterface $output = null): void
240
    {
241
        if ($output) {
242
            $this->logger->addLogger(new DateTimeFormatter(new ConsoleLogger($output)));
243
        }
244
        $codeCheckDirectory = FileChangesHelper::checkDirectory($this->dirPathHistoryAppointments);
245
        if ($codeCheckDirectory === 'no_directory') {
246
            $this->logger->error('Path ' . $this->dirPathHistoryAppointments . ' doesn\'t exists.');
247
            return;
248
        }
249
        if ($codeCheckDirectory === 'not_readable') {
250
            $this->logger->error('Path ' . $this->dirPathHistoryAppointments . ' is not readable.');
251
            return;
252
        }
253
        if ($codeCheckDirectory === 'not_writable') {
254
            $this->logger->error('Path ' . $this->dirPathHistoryAppointments . ' is not writable.');
255
            return;
256
        }
257
        $this->lock->acquireLock();
258
        $this->logger->info('Detect changes - Run.');
259
        //That permits to stop the followings instructions when we are makings changes on Certain.
260
        //Get the online appointments.
261
        $appointmentsNewCertain = $this->getCurrentAppoiments($eventCode);
262
        $appointmentsNew = self::recursiveArrayObjectToFullArray($appointmentsNewCertain);
263
        //Get the last saved appointments to get old data.
264
        $appointmentsOldHistoryFilePath = FileChangesHelper::getTheLastAppointmentsSaved($eventCode, $this->dirPathHistoryAppointments);
265
        if (!$appointmentsOldHistoryFilePath) {
266
            //No files so it's the first time we attempt to synchronize.
267
            $appointmentsOld = [];
268
        } else {
269
            //Get the last old appointments data.
270
            $appointmentsOldHistory = FileChangesHelper::getJsonContentFromFile($appointmentsOldHistoryFilePath);
271
            $appointmentsOld = self::recursiveArrayObjectToFullArray($appointmentsOldHistory);
272
        }
273
        //Check if they are changes.
274
        $timestamp = time();
275
        $listChangings = $this->detectAppointmentsChangings($appointmentsOld, $appointmentsNew, $timestamp);
276
        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...
277
                || (isset($listChangings['deleted']) && !empty($listChangings['deleted'])))) {
278
            //Changes? So we save the new online appointments
279
            FileChangesHelper::saveAppointmentsFileByHistory($this->dirPathHistoryAppointments . '/appointments_' . $eventCode . '.json', json_encode($appointmentsNew));
280
            $this->logger->info('Detect changes - Save Changes');
281
        } else {
282
            $this->logger->info('Detect changes - No Changes');
283
        }
284
        foreach ($this->listeners as $listener) {
285
            //Run Listener. For instance,Here we can use ChangingsToFileListeners to save the changes in file.
286
            $listener->run($eventCode, $listChangings);
287
        }
288
        $this->logger->info('Detect changes - Stop.');
289
        $this->lock->releaseLock();
290
    }
291
292
}