1 | <?php |
||||
2 | |||||
3 | namespace Slides\Connector\Auth\Sync; |
||||
4 | |||||
5 | use Illuminate\Support\Arr; |
||||
6 | use Illuminate\Support\Collection; |
||||
7 | use Slides\Connector\Auth\Sync\Syncable as LocalUser; |
||||
8 | use Slides\Connector\Auth\Sync\User as RemoteUser; |
||||
9 | use Slides\Connector\Auth\Client; |
||||
10 | use Slides\Connector\Auth\AuthService; |
||||
11 | |||||
12 | /** |
||||
13 | * Class Syncer |
||||
14 | * |
||||
15 | * @package Slides\Connector\Auth\Sync |
||||
16 | */ |
||||
17 | class Syncer |
||||
18 | { |
||||
19 | use HandlesActions, |
||||
20 | ExportsUsers, |
||||
21 | ImportsUsers; |
||||
22 | |||||
23 | /** |
||||
24 | * Number of users which can be sent per request |
||||
25 | */ |
||||
26 | const USERS_PER_REQUEST = 5000; |
||||
27 | |||||
28 | /** |
||||
29 | * Synchronization modes. |
||||
30 | * |
||||
31 | * `passwords` — allows updating passwords locally and remotely. |
||||
32 | * `users` — allows syncing specific users only. |
||||
33 | */ |
||||
34 | const MODE_PASSWORDS = 'passwords'; |
||||
35 | const MODE_USERS = 'users'; |
||||
36 | |||||
37 | /** |
||||
38 | * The authentication service. |
||||
39 | * |
||||
40 | * @var AuthService |
||||
41 | */ |
||||
42 | protected $authService; |
||||
43 | |||||
44 | /** |
||||
45 | * Authentication Service client. |
||||
46 | * |
||||
47 | * @var Client |
||||
48 | */ |
||||
49 | protected $client; |
||||
50 | |||||
51 | /** |
||||
52 | * The local users for syncing remotely. |
||||
53 | * |
||||
54 | * @var LocalUser[]|Collection |
||||
55 | */ |
||||
56 | protected $locals; |
||||
57 | |||||
58 | /** |
||||
59 | * The remote users fetched. |
||||
60 | * |
||||
61 | * @var RemoteUser[]|Collection |
||||
62 | */ |
||||
63 | protected $foreigners; |
||||
64 | |||||
65 | /** |
||||
66 | * The sync modes. |
||||
67 | * |
||||
68 | * @var array |
||||
69 | */ |
||||
70 | protected $modes; |
||||
71 | |||||
72 | /** |
||||
73 | * The remote statistics. |
||||
74 | * |
||||
75 | * @var array |
||||
76 | */ |
||||
77 | protected $remoteStats = [ |
||||
78 | 'created' => 0, |
||||
79 | 'updated' => 0, |
||||
80 | 'deleted' => 0 |
||||
81 | ]; |
||||
82 | |||||
83 | /** |
||||
84 | * The local statistics. |
||||
85 | * |
||||
86 | * @var array |
||||
87 | */ |
||||
88 | protected $localStats = [ |
||||
89 | 'created' => 0, |
||||
90 | 'updated' => 0, |
||||
91 | 'deleted' => 0 |
||||
92 | ]; |
||||
93 | |||||
94 | /** |
||||
95 | * Output messages. |
||||
96 | * |
||||
97 | * @var array |
||||
98 | */ |
||||
99 | protected $output = []; |
||||
100 | |||||
101 | /** |
||||
102 | * The callback called on adding a message to the output. |
||||
103 | * |
||||
104 | * @var \Closure |
||||
105 | */ |
||||
106 | protected $outputCallback; |
||||
107 | |||||
108 | /** |
||||
109 | * Syncer constructor. |
||||
110 | * |
||||
111 | * @param LocalUser[]|Collection|null $locals |
||||
112 | * @param array $modes |
||||
113 | * @param Client|null $client |
||||
114 | */ |
||||
115 | public function __construct(Collection $locals = null, array $modes = [], Client $client = null) |
||||
116 | { |
||||
117 | $this->locals = $locals ?? collect(); |
||||
118 | $this->foreigners = collect(); |
||||
119 | $this->modes = $modes; |
||||
120 | $this->client = $client ?? new Client(); |
||||
121 | $this->authService = app('authService'); |
||||
0 ignored issues
–
show
|
|||||
122 | } |
||||
123 | |||||
124 | /** |
||||
125 | * Synchronize local users remotely and apply changes. |
||||
126 | * |
||||
127 | * @return void |
||||
128 | */ |
||||
129 | public function sync() |
||||
130 | { |
||||
131 | $iterator = new UserGroupsIterator($this->locals, static::USERS_PER_REQUEST); |
||||
132 | |||||
133 | $this->outputMessage('Total requests: ' . $iterator->requestsCount()); |
||||
134 | |||||
135 | /** @var LocalUser[]|Collection $users */ |
||||
136 | foreach ($iterator as $users) { |
||||
137 | $this->outputMessage('Sending a request with bunch of ' . $users->count() . ' users'); |
||||
138 | |||||
139 | $response = $this->client->request('sync', [ |
||||
140 | 'users' => $this->formatLocals($users), |
||||
141 | 'modes' => $this->modes |
||||
142 | ]); |
||||
143 | |||||
144 | $this->outputMessage('Parsing a response...'); |
||||
145 | |||||
146 | $this->parseResponse($response); |
||||
147 | } |
||||
148 | |||||
149 | $this->outputMessage("Applying {$this->foreigners->count()} remote changes locally"); |
||||
150 | |||||
151 | $this->apply(); |
||||
152 | } |
||||
153 | |||||
154 | /** |
||||
155 | * Parse a response. |
||||
156 | * |
||||
157 | * @param array $response |
||||
158 | */ |
||||
159 | protected function parseResponse(array $response) |
||||
160 | { |
||||
161 | $foreigners = array_map(function (array $user) { |
||||
162 | return $this->createRemoteUserFromResponse($user); |
||||
163 | }, Arr::get($response, 'difference')); |
||||
0 ignored issues
–
show
It seems like
Illuminate\Support\Arr::...response, 'difference') can also be of type null ; however, parameter $arr1 of array_map() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
164 | |||||
165 | $this->mergeRemoteStats($remoteStats = Arr::get($response, 'stats')); |
||||
0 ignored issues
–
show
It seems like
$remoteStats = Illuminat...get($response, 'stats') can also be of type null ; however, parameter $stats of Slides\Connector\Auth\Sy...cer::mergeRemoteStats() does only seem to accept array , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
166 | |||||
167 | $this->outputMessage( |
||||
168 | 'Remote affection:' |
||||
169 | . ' created ' . $remoteStats['created'] |
||||
170 | . ', updated ' . $remoteStats['updated'] |
||||
171 | . ', deleted ' . $remoteStats['deleted'] |
||||
172 | ); |
||||
173 | |||||
174 | $this->foreigners = $this->foreigners->merge($foreigners); |
||||
175 | } |
||||
176 | |||||
177 | /** |
||||
178 | * Apply changes locally from the given response. |
||||
179 | * |
||||
180 | * @return void |
||||
181 | */ |
||||
182 | public function apply() |
||||
183 | { |
||||
184 | $count = $this->getForeignersCount(); |
||||
185 | |||||
186 | foreach ($this->foreigners as $index => $foreigner) { |
||||
187 | $index++; |
||||
188 | |||||
189 | $this->outputMessage( |
||||
190 | "[$index of $count] Handling the action \"" . $foreigner->getRemoteAction() . '"' |
||||
191 | . ' of ' . $foreigner->getName() |
||||
192 | . ' (' . $foreigner->getEmail() . ')' |
||||
193 | ); |
||||
194 | |||||
195 | try { |
||||
196 | $this->handleAction( |
||||
197 | $foreigner, |
||||
198 | $action = $foreigner->getRemoteAction() |
||||
199 | ); |
||||
200 | } |
||||
201 | catch(\Slides\Connector\Auth\Exceptions\SyncException $e) { |
||||
202 | \Illuminate\Support\Facades\Log::error( |
||||
203 | "Cannot $action the user {$foreigner->getEmail()}: " . $e->getMessage() |
||||
204 | ); |
||||
205 | } |
||||
206 | } |
||||
207 | } |
||||
208 | |||||
209 | /** |
||||
210 | * Format local users for a request payload. |
||||
211 | * |
||||
212 | * @param Collection $locals |
||||
213 | * |
||||
214 | * @return array |
||||
215 | */ |
||||
216 | private function formatLocals(Collection $locals) |
||||
217 | { |
||||
218 | return $locals |
||||
219 | ->map(function(Syncable $user) { |
||||
220 | return [ |
||||
221 | 'id' => $user->retrieveId(), |
||||
222 | 'remoteId' => $user->retrieveRemoteId(), |
||||
223 | 'name' => $user->retrieveName(), |
||||
224 | 'email' => $user->retrieveEmail(), |
||||
225 | 'password' => $user->retrievePassword(), |
||||
226 | 'country' => $user->retrieveCountry(), |
||||
227 | 'created_at' => $user->retrieveCreatedAt()->toDateTimeString(), |
||||
228 | 'updated_at' => $user->retrieveUpdatedAt()->toDateTimeString(), |
||||
229 | 'deleted_at' => $user->retrieveDeletedAt() |
||||
230 | ? $user->retrieveDeletedAt()->toDateTimeString() |
||||
231 | : null |
||||
232 | ]; |
||||
233 | }) |
||||
234 | ->toArray(); |
||||
235 | } |
||||
236 | |||||
237 | /** |
||||
238 | * Create a remote user from the response. |
||||
239 | * |
||||
240 | * @param array $user |
||||
241 | * |
||||
242 | * @return User |
||||
243 | */ |
||||
244 | public function createRemoteUserFromResponse(array $user) |
||||
245 | { |
||||
246 | return new RemoteUser( |
||||
247 | Arr::get($user, 'id'), |
||||
0 ignored issues
–
show
It seems like
Illuminate\Support\Arr::get($user, 'id') can also be of type null ; however, parameter $remoteId of Slides\Connector\Auth\Sync\User::__construct() does only seem to accept integer , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
248 | Arr::get($user, 'name'), |
||||
249 | Arr::get($user, 'email'), |
||||
0 ignored issues
–
show
It seems like
Illuminate\Support\Arr::get($user, 'email') can also be of type null ; however, parameter $email of Slides\Connector\Auth\Sync\User::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
250 | Arr::get($user, 'password'), |
||||
251 | Arr::get($user, 'updated_at'), |
||||
252 | Arr::get($user, 'created_at'), |
||||
0 ignored issues
–
show
It seems like
Illuminate\Support\Arr::get($user, 'created_at') can also be of type null ; however, parameter $created of Slides\Connector\Auth\Sync\User::__construct() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
253 | Arr::get($user, 'deleted_at'), |
||||
254 | Arr::get($user, 'country'), |
||||
255 | Arr::get($user, 'action') |
||||
256 | ); |
||||
257 | } |
||||
258 | |||||
259 | /** |
||||
260 | * Check whether a mode is passed. |
||||
261 | * |
||||
262 | * @param string $mode |
||||
263 | * |
||||
264 | * @return bool |
||||
265 | */ |
||||
266 | public function hasMode(string $mode): bool |
||||
267 | { |
||||
268 | return array_key_exists($mode, $this->modes); |
||||
269 | } |
||||
270 | |||||
271 | /** |
||||
272 | * Get passed modes. |
||||
273 | * |
||||
274 | * @return array |
||||
275 | */ |
||||
276 | public function getModes(): array |
||||
277 | { |
||||
278 | return $this->modes; |
||||
279 | } |
||||
280 | |||||
281 | /** |
||||
282 | * Check if there are difference detected by remote service. |
||||
283 | * |
||||
284 | * @return bool |
||||
285 | */ |
||||
286 | public function hasDifference(): bool |
||||
287 | { |
||||
288 | return $this->foreigners->isNotEmpty(); |
||||
289 | } |
||||
290 | |||||
291 | /** |
||||
292 | * Get the local stats. |
||||
293 | * |
||||
294 | * @return array |
||||
295 | */ |
||||
296 | public function getLocalStats(): array |
||||
297 | { |
||||
298 | return $this->localStats; |
||||
299 | } |
||||
300 | |||||
301 | /** |
||||
302 | * Get the remote stats. |
||||
303 | * |
||||
304 | * @return array |
||||
305 | */ |
||||
306 | public function getRemoteStats(): array |
||||
307 | { |
||||
308 | return $this->remoteStats; |
||||
309 | } |
||||
310 | |||||
311 | /** |
||||
312 | * Set remote users. |
||||
313 | * |
||||
314 | * @param Collection|RemoteUser[] $foreigners |
||||
315 | */ |
||||
316 | public function setForeigners(Collection $foreigners): void |
||||
317 | { |
||||
318 | $this->foreigners = $foreigners; |
||||
319 | } |
||||
320 | |||||
321 | /** |
||||
322 | * Get number of foreign users. |
||||
323 | * |
||||
324 | * @return int |
||||
325 | */ |
||||
326 | public function getForeignersCount(): int |
||||
327 | { |
||||
328 | return $this->foreigners->count(); |
||||
329 | } |
||||
330 | |||||
331 | /** |
||||
332 | * Retrieve all local users. |
||||
333 | * |
||||
334 | * @return Collection |
||||
335 | */ |
||||
336 | public static function retrieveLocals(): Collection |
||||
337 | { |
||||
338 | return \Illuminate\Support\Facades\Auth::getProvider()->createModel() |
||||
339 | ->newQuery() |
||||
340 | ->get(); |
||||
341 | } |
||||
342 | |||||
343 | /** |
||||
344 | * Merge the remote stats. |
||||
345 | * |
||||
346 | * @param array $stats |
||||
347 | */ |
||||
348 | private function mergeRemoteStats(array $stats) |
||||
349 | { |
||||
350 | $this->remoteStats['created'] += $stats['created']; |
||||
351 | $this->remoteStats['updated'] += $stats['updated']; |
||||
352 | $this->remoteStats['deleted'] += $stats['deleted']; |
||||
353 | } |
||||
354 | |||||
355 | /** |
||||
356 | * Add a message to the output |
||||
357 | * |
||||
358 | * @param string $message |
||||
359 | * |
||||
360 | * @return void |
||||
361 | */ |
||||
362 | private function outputMessage(string $message) |
||||
363 | { |
||||
364 | $output[] = $message; |
||||
0 ignored issues
–
show
Comprehensibility
Best Practice
introduced
by
|
|||||
365 | |||||
366 | if($this->outputCallback instanceof \Closure) { |
||||
0 ignored issues
–
show
|
|||||
367 | call_user_func($this->outputCallback, $message); |
||||
368 | } |
||||
369 | } |
||||
370 | |||||
371 | /** |
||||
372 | * Set a callback which should be called on adding output message. |
||||
373 | * |
||||
374 | * @param \Closure $outputCallback |
||||
375 | * |
||||
376 | * @return void |
||||
377 | */ |
||||
378 | public function setOutputCallback(\Closure $outputCallback): void |
||||
379 | { |
||||
380 | $this->outputCallback = $outputCallback; |
||||
381 | } |
||||
382 | |||||
383 | /** |
||||
384 | * Increment a local stats value. |
||||
385 | * |
||||
386 | * @param string $key |
||||
387 | */ |
||||
388 | protected function incrementStats(string $key) |
||||
389 | { |
||||
390 | $value = Arr::get($this->localStats, $key, 0); |
||||
391 | |||||
392 | $this->localStats[$key] = ++$value; |
||||
393 | } |
||||
394 | } |
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.