AchievementsStorage::dispatchEvent()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
namespace Laravel\Achievements;
4
5
use Carbon\Carbon;
6
use Illuminate\Contracts\Config\Repository;
7
use Illuminate\Database\Eloquent\Collection;
8
use Illuminate\Support\Facades\Event;
9
use Zurbaev\Achievements\Achievement;
10
use Zurbaev\Achievements\AchievementCriteria;
11
use Zurbaev\Achievements\AchievementCriteriaProgress;
12
use Zurbaev\Achievements\Contracts\AchievementsStorageInterface;
13
14
class AchievementsStorage implements AchievementsStorageInterface
15
{
16
    const MODEL_ACHIEVEMENT = 'achievement';
17
    const MODEL_CRITERIA = 'criteria';
18
19
    /**
20
     * @var Repository
21
     */
22
    protected $config;
23
24
    /**
25
     * AchievementsStorage constructor.
26
     *
27
     * @param Repository $config
28
     */
29
    public function __construct(Repository $config)
30
    {
31
        $this->config = $config;
32
    }
33
34
    /**
35
     * Returns actual class name from config.
36
     *
37
     * @param string $section
38
     * @param string $type
39
     *
40
     * @return string
41
     */
42
    protected function getConfigurableClassName(string $section, string $type)
43
    {
44
        return $this->config->get('achievements.'.$section.'.'.$type);
45
    }
46
47
    /**
48
     * Injects model class name into given callback.
49
     *
50
     * @param string   $type
51
     * @param callable $callback
52
     *
53
     * @return mixed|string
54
     */
55
    protected function getModelClass(string $type, callable $callback)
56
    {
57
        return call_user_func($callback, $this->getConfigurableClassName('models', $type));
58
    }
59
60
    /**
61
     * Dispatches new Achievements event.
62
     *
63
     * @param string $type
64
     * @param array $args
65
     * @return mixed
66
     */
67
    protected function dispatchEvent(string $type, array $args = [])
68
    {
69
        $className = $this->getConfigurableClassName('events', $type);
70
71
        return Event::dispatch(new $className(...$args));
72
    }
73
74
    /**
75
     * Fetches given owner's criterias by given type.
76
     *
77
     * @param mixed  $owner
78
     * @param string $type
79
     * @param mixed $data = null
80
     *
81
     * @return array
82
     */
83
    public function getOwnerCriteriasByType($owner, string $type, $data = null)
84
    {
85
        $criterias = $this->getCriteriasByType($type, $data);
86
87
        if (!count($criterias)) {
88
            return [];
89
        }
90
91
        $ownerCriteriaProgress = $this->getOwnerCriteriasProgress($owner, function ($query) use ($type) {
92
            $query->where('type', $type);
93
        });
94
95
        return $criterias->map(function ($criteria) use ($ownerCriteriaProgress) {
96
            /** @var AchievementCriteriaModel $criteria */
97
98
            return $this->transformCriteriaWithProgress($criteria, $ownerCriteriaProgress->get($criteria->id));
99
        })->toArray();
100
    }
101
102
    /**
103
     * Loads criteria progress for given owner, applies given query callback
104
     * and returns array of AchievementCriteria objects transformed from AchievementCriteriaModels.
105
     *
106
     * @param mixed    $owner
107
     * @param callable $callback
108
     *
109
     * @return \Illuminate\Support\Collection
110
     */
111
    protected function getOwnerCriteriasProgress($owner, callable $callback)
112
    {
113
        $query = $owner->achievementCriterias();
114
115
        call_user_func_array($callback, [$query]);
116
117
        return $this->transformOwnerCriteriasToProgress($query->get());
118
    }
119
120
    /**
121
     * Loads achievement criterias with given type.
122
     *
123
     * @param string $type
124
     * @param mixed $data = null
125
     *
126
     * @return \Illuminate\Database\Eloquent\Collection
127
     */
128
    protected function getCriteriasByType(string $type, $data = null)
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
129
    {
130
        return $this->getModelClass(static::MODEL_CRITERIA, function (string $className) use ($type) {
131
            return $className::where('type', $type)->get();
132
        });
133
    }
134
135
    /**
136
     * Transforms AchievementCriteriaModel collection int AchievementCriteriaProgress collection.
137
     *
138
     * @param Collection $criterias
139
     *
140
     * @return \Illuminate\Support\Collection
141
     */
142
    protected function transformOwnerCriteriasToProgress(Collection $criterias)
143
    {
144
        return $criterias->keyBy('id')->map(function ($criteria) {
145
            /** @var AchievementCriteriaModel $criteria */
146
147
            return new AchievementCriteriaProgress(
148
                intval($criteria->pivot->value),
149
                false,
150
                intval($criteria->pivot->completed) === 1,
151
                is_string($criteria->pivot->progress_data) ? json_decode($criteria->pivot->progress_data, true) : []
152
            );
153
        });
154
    }
155
156
    /**
157
     * Transforms AchievementCriteriaModel to AchievementCriteria object and attaches progress data.
158
     *
159
     * @param AchievementCriteriaModel         $criteria
160
     * @param AchievementCriteriaProgress|null $progress
161
     *
162
     * @return AchievementCriteria
163
     */
164
    protected function transformCriteriaWithProgress($criteria, AchievementCriteriaProgress $progress = null)
165
    {
166
        $data = [
167
            'id' => $criteria->id,
168
            'achievement_id' => $criteria->achievement_id,
169
            'type' => $criteria->type,
170
            'name' => $criteria->name,
171
            'requirements' => $criteria->requirements ?? [],
172
            'max_value' => $criteria->max_value,
173
        ];
174
175
        if (!is_null($progress)) {
176
            $data['progress'] = [
177
                'value' => $progress->value,
178
                'changed' => false,
179
                'completed' => $progress->completed,
180
                'data' => $progress->data,
181
            ];
182
        }
183
184
        return new AchievementCriteria($data);
185
    }
186
187
    /**
188
     * Returns list of criterias' achievements.
189
     *
190
     * @param array $criterias
191
     *
192
     * @return array
193
     */
194
    public function getAchievementsByCriterias(array $criterias)
195
    {
196
        $achievementIds = array_map(function (AchievementCriteria $criteria) {
197
            return $criteria->achievementId();
198
        }, $criterias);
199
200
        $achievements = $this->getAchievementsByIds(array_unique($achievementIds));
201
202
        if (!count($achievements)) {
203
            return [];
204
        }
205
206
        return $this->transformAchievements($achievements, $criterias)->toArray();
207
    }
208
209
    /**
210
     * Loads collection of achievements by IDs.
211
     *
212
     * @param array $achievementIds
213
     *
214
     * @return mixed
215
     */
216
    public function getAchievementsByIds(array $achievementIds)
217
    {
218
        return $this->getModelClass(static::MODEL_ACHIEVEMENT, function (string $className) use ($achievementIds) {
219
            return $className::whereIn('id', array_unique($achievementIds))->get();
220
        });
221
    }
222
223
    /**
224
     * Converts collection of AchievementModel objects to array of Achievement objects.
225
     *
226
     * @param Collection $achievements
227
     * @param bool       $reloadCriteriasRelation = true
228
     *
229
     * @return array
230
     */
231
    public function convertAchievementModelsWithCriterias(Collection $achievements, bool $reloadCriteriasRelation = true)
232
    {
233
        if ($reloadCriteriasRelation) {
234
            $achievements->load('criterias');
235
        }
236
237
        return $achievements->map([$this, 'convertAchievementModelWithCriterias'])->toArray();
238
    }
239
240
    /**
241
     * Converts single AchievementModel to Achievement object.
242
     *
243
     * @param AchievementModel $achievement
244
     *
245
     * @return Achievement
246
     */
247
    public function convertAchievementModelWithCriterias($achievement)
248
    {
249
        $criterias = $achievement->criterias->map(function ($criteria) {
250
            /** @var AchievementCriteriaModel $criteria */
251
252
            return $this->transformCriteriaWithProgress($criteria);
253
        });
254
255
        return $this->transformSingleAchievement($achievement, $criterias->toArray());
256
    }
257
258
    /**
259
     * Transforms AchievementModel collection to Achievement collection and applies appropriate criterias.
260
     *
261
     * @param Collection $achievements
262
     * @param array      $criterias
263
     *
264
     * @return \Illuminate\Support\Collection
265
     */
266
    protected function transformAchievements(Collection $achievements, array $criterias)
267
    {
268
        return $achievements->map(function ($achievement) use ($criterias) {
269
            /** @var AchievementModel $achievement */
270
271
            return $this->transformSingleAchievement($achievement, $criterias);
272
        });
273
    }
274
275
    /**
276
     * Transforms single AchievementModel object to Achievement object with appropriate criterias.
277
     *
278
     * @param AchievementModel $achievement
279
     * @param array            $criterias
280
     *
281
     * @return Achievement
282
     */
283
    protected function transformSingleAchievement($achievement, array $criterias)
284
    {
285
        $achievementCriterias = array_filter($criterias, function (AchievementCriteria $criteria) use ($achievement) {
286
            return $criteria->achievementId() === $achievement->id;
287
        });
288
289
        // Since we're dealing with owner-related criterias (progress exists if owner has any value),
290
        // we can simply count completed criterias & determine if achievement has been completed.
291
        $completedCriteriasCount = array_sum(
292
            array_map(function (AchievementCriteria $criteria) {
293
                return $criteria->completed() ? 1 : 0;
294
            }, $achievementCriterias)
295
        );
296
297
        return new Achievement([
298
            'id' => $achievement->id,
299
            'name' => $achievement->name,
300
            'description' => $achievement->description,
301
            'points' => $achievement->points,
302
            'completed' => count($achievementCriterias) === $completedCriteriasCount,
303
            'criterias' => $achievementCriterias,
304
        ]);
305
    }
306
307
    /**
308
     * Extracts single Achievement from given list for given criteria.
309
     *
310
     * @param AchievementCriteria $criteria
311
     * @param array               $achievements
312
     *
313
     * @return Achievement
314
     */
315
    public function getAchievementForCriteria(AchievementCriteria $criteria, array $achievements)
316
    {
317
        $collection = collect($achievements);
318
319
        $index = $collection->search(function (Achievement $achievement) use ($criteria) {
320
            return $achievement->id() === $criteria->achievementId();
321
        });
322
323
        if ($index === false) {
324
            throw new \InvalidArgumentException('Achievement for criteria #'.$criteria->id().' was not found.');
325
        }
326
327
        return $collection->get($index);
328
    }
329
330
    /**
331
     * Loads achievements with progresses for given owner.
332
     *
333
     * @param mixed $owner
334
     * @param array $achievementIds
335
     *
336
     * @return array
337
     */
338
    public function getAchievementsWithProgressFor($owner, array $achievementIds)
339
    {
340
        $criterias = $this->getCriteriasByAchievementIds($achievementIds);
341
342
        if (!count($criterias)) {
343
            return [];
344
        }
345
346
        $ownerCriteriaProgress = $this->getOwnerCriteriasProgress($owner, function ($query) use ($criterias) {
347
            $query->whereIn('achievement_criteria_model_id', $criterias->pluck('id'));
348
        });
349
350
        $achievementsCriterias = $criterias->map(function ($criteria) use ($ownerCriteriaProgress) {
351
            /** @var AchievementCriteriaModel $criteria */
352
353
            return $this->transformCriteriaWithProgress($criteria, $ownerCriteriaProgress->get($criteria->id));
354
        });
355
356
        return $this->getAchievementsByCriterias($achievementsCriterias->toArray());
357
    }
358
359
    /**
360
     * Loads criterias by achievement IDs.
361
     *
362
     * @param array $achievementIds
363
     *
364
     * @return \Illuminate\Database\Eloquent\Collection
365
     */
366
    public function getCriteriasByAchievementIds(array $achievementIds)
367
    {
368
        return $this->getModelClass(static::MODEL_CRITERIA, function (string $className) use ($achievementIds) {
369
            return $className::whereIn('achievement_id', $achievementIds)->get();
370
        });
371
    }
372
373
    /**
374
     * Saves criteria progress for given owner.
375
     *
376
     * @param mixed                       $owner
377
     * @param AchievementCriteria         $criteria
378
     * @param Achievement                 $achievement
379
     * @param AchievementCriteriaProgress $progress
380
     *
381
     * @return mixed
382
     */
383
    public function setCriteriaProgressUpdated($owner, AchievementCriteria $criteria, Achievement $achievement, AchievementCriteriaProgress $progress)
384
    {
385
        $owner->achievementCriterias()->syncWithoutDetaching([
386
            $criteria->id() => [
387
                'value' => $progress->value,
388
                'completed' => $progress->completed,
389
                'progress_data' => json_encode($progress->data),
390
            ],
391
        ]);
392
393
        $this->dispatchEvent('criteria_updated', [$owner, $criteria, $achievement, $progress]);
394
395
        return true;
396
    }
397
398
    /**
399
     * Saves given achievements completeness state for given owner.
400
     *
401
     * @param mixed $owner
402
     * @param array $achievements
403
     *
404
     * @return mixed
405
     */
406
    public function setAchievementsCompleted($owner, array $achievements)
407
    {
408
        $now = Carbon::now();
409
        $patch = [];
410
411
        foreach ($achievements as $achievement) {
412
            $patch[$achievement->id()] = ['completed_at' => $now];
413
        }
414
415
        $owner->achievements()->syncWithoutDetaching($patch);
416
417
        $this->dispatchEvent('achievements_completed', [$owner, $achievements]);
418
419
        return true;
420
    }
421
}
422