Completed
Push — master ( 8e2899...443011 )
by Timur
01:27
created

convertAchievementModelsWithCriterias()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
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 Laravel\Achievements\Events\AchievementsCompleted;
10
use Laravel\Achievements\Events\CriteriaUpdated;
11
use Zurbaev\Achievements\Achievement;
12
use Zurbaev\Achievements\AchievementCriteria;
13
use Zurbaev\Achievements\AchievementCriteriaProgress;
14
use Zurbaev\Achievements\Contracts\AchievementsStorageInterface;
15
16
class AchievementsStorage implements AchievementsStorageInterface
17
{
18
    const MODEL_ACHIEVEMENT = 'achievement';
19
    const MODEL_CRITERIA = 'criteria';
20
21
    /**
22
     * @var Repository
23
     */
24
    protected $config;
25
26
    /**
27
     * AchievementsStorage constructor.
28
     *
29
     * @param Repository $config
30
     */
31
    public function __construct(Repository $config)
32
    {
33
        $this->config = $config;
34
    }
35
36
    /**
37
     * Injects actual model class name into given callback.
38
     *
39
     * @param string   $type
40
     * @param callable $callback
41
     *
42
     * @return mixed
43
     */
44
    protected function getModelClass(string $type, callable $callback)
45
    {
46
        return call_user_func($callback, $this->config->get('achievements.models.'.$type));
47
    }
48
49
    /**
50
     * Fetches given owner's criterias by given type.
51
     *
52
     * @param mixed  $owner
53
     * @param string $type
54
     *
55
     * @return array
56
     */
57
    public function getOwnerCriteriasByType($owner, string $type)
58
    {
59
        $criterias = $this->getCriteriasByType($type);
60
61
        if (!count($criterias)) {
62
            return [];
63
        }
64
65
        $ownerCriteriaProgress = $this->getOwnerCriteriasProgress($owner, function ($query) use ($type) {
66
            $query->where('type', $type);
67
        });
68
69
        return $criterias->map(function ($criteria) use ($ownerCriteriaProgress) {
70
            /** @var AchievementCriteriaModel $criteria */
71
72
            return $this->transformCriteriaWithProgress($criteria, $ownerCriteriaProgress->get($criteria->id));
73
        })->toArray();
74
    }
75
76
    /**
77
     * Loads criteria progress for given owner, applies given query callback
78
     * and returns array of AchievementCriteria objects transformed from AchievementCriteriaModels.
79
     *
80
     * @param mixed    $owner
81
     * @param callable $callback
82
     *
83
     * @return \Illuminate\Support\Collection
84
     */
85
    protected function getOwnerCriteriasProgress($owner, callable $callback)
86
    {
87
        $query = $owner->achievementCriterias();
88
89
        call_user_func_array($callback, [$query]);
90
91
        return $this->transformOwnerCriteriasToProgress($query->get());
92
    }
93
94
    /**
95
     * Loads achievement criterias with given type.
96
     *
97
     * @param string $type
98
     *
99
     * @return \Illuminate\Database\Eloquent\Collection
100
     */
101
    protected function getCriteriasByType(string $type)
102
    {
103
        return $this->getModelClass(static::MODEL_CRITERIA, function (string $className) use ($type) {
104
            return $className::where('type', $type)->get();
105
        });
106
    }
107
108
    /**
109
     * Transforms AchievementCriteriaModel collection int AchievementCriteriaProgress collection.
110
     *
111
     * @param Collection $criterias
112
     *
113
     * @return \Illuminate\Support\Collection
114
     */
115
    protected function transformOwnerCriteriasToProgress(Collection $criterias)
116
    {
117
        return $criterias->keyBy('id')->map(function ($criteria) {
118
            /** @var AchievementCriteriaModel $criteria */
119
120
            return new AchievementCriteriaProgress(
121
                intval($criteria->pivot->value),
122
                false,
123
                intval($criteria->pivot->completed) === 1,
124
                is_string($criteria->pivot->progress_data) ? json_decode($criteria->pivot->progress_data, true) : []
125
            );
126
        });
127
    }
128
129
    /**
130
     * Transforms AchievementCriteriaModel to AchievementCriteria object and attaches progress data.
131
     *
132
     * @param AchievementCriteriaModel         $criteria
133
     * @param AchievementCriteriaProgress|null $progress
134
     *
135
     * @return AchievementCriteria
136
     */
137
    protected function transformCriteriaWithProgress($criteria, AchievementCriteriaProgress $progress = null)
138
    {
139
        $data = [
140
            'id' => $criteria->id,
141
            'achievement_id' => $criteria->achievement_id,
142
            'type' => $criteria->type,
143
            'name' => $criteria->name,
144
            'requirements' => $criteria->requirements ?? [],
145
            'max_value' => $criteria->max_value,
146
        ];
147
148
        if (!is_null($progress)) {
149
            $data['progress'] = [
150
                'value' => $progress->value,
151
                'changed' => false,
152
                'completed' => $progress->completed,
153
                'data' => $progress->data,
154
            ];
155
        }
156
157
        return new AchievementCriteria($data);
158
    }
159
160
    /**
161
     * Returns list of criterias' achievements.
162
     *
163
     * @param array $criterias
164
     *
165
     * @return array
166
     */
167
    public function getAchievementsByCriterias(array $criterias)
168
    {
169
        $achievementIds = array_map(function (AchievementCriteria $criteria) {
170
            return $criteria->achievementId();
171
        }, $criterias);
172
173
        $achievements = $this->getAchievementsByIds(array_unique($achievementIds));
174
175
        if (!count($achievements)) {
176
            return [];
177
        }
178
179
        return $this->transformAchievements($achievements, $criterias)->toArray();
180
    }
181
182
    /**
183
     * Loads collection of achievements by IDs.
184
     *
185
     * @param array $achievementIds
186
     *
187
     * @return mixed
188
     */
189
    public function getAchievementsByIds(array $achievementIds)
190
    {
191
        return $this->getModelClass(static::MODEL_ACHIEVEMENT, function (string $className) use ($achievementIds) {
192
            return $className::whereIn('id', array_unique($achievementIds))->get();
193
        });
194
    }
195
196
    /**
197
     * Converts collection of AchievementModel objects to array of Achievement objects.
198
     *
199
     * @param Collection $achievements
200
     * @param bool       $reloadCriteriasRelation = true
201
     *
202
     * @return array
203
     */
204
    public function convertAchievementModelsWithCriterias(Collection $achievements, bool $reloadCriteriasRelation = true)
205
    {
206
        if ($reloadCriteriasRelation) {
207
            $achievements->load('criterias');
208
        }
209
210
        return $achievements->map([$this, 'convertAchievementModelWithCriterias'])->toArray();
211
    }
212
213
    /**
214
     * Converts single AchievementModel to Achievement object.
215
     *
216
     * @param AchievementModel $achievement
217
     *
218
     * @return Achievement
219
     */
220
    public function convertAchievementModelWithCriterias($achievement)
221
    {
222
        $criterias = $achievement->criterias->map(function ($criteria) {
223
            /** @var AchievementCriteriaModel $criteria */
224
225
            return $this->transformCriteriaWithProgress($criteria);
226
        });
227
228
        return $this->transformSingleAchievement($achievement, $criterias->toArray());
229
    }
230
231
    /**
232
     * Transforms AchievementModel collection to Achievement collection and applies appropriate criterias.
233
     *
234
     * @param Collection $achievements
235
     * @param array      $criterias
236
     *
237
     * @return \Illuminate\Support\Collection
238
     */
239
    protected function transformAchievements(Collection $achievements, array $criterias)
240
    {
241
        return $achievements->map(function ($achievement) use ($criterias) {
242
            /** @var AchievementModel $achievement */
243
244
            return $this->transformSingleAchievement($achievement, $criterias);
245
        });
246
    }
247
248
    /**
249
     * Transforms single AchievementModel object to Achievement object with appropriate criterias.
250
     *
251
     * @param AchievementModel $achievement
252
     * @param array            $criterias
253
     *
254
     * @return Achievement
255
     */
256
    protected function transformSingleAchievement($achievement, array $criterias)
257
    {
258
        $achievementCriterias = array_filter($criterias, function (AchievementCriteria $criteria) use ($achievement) {
259
            return $criteria->achievementId() === $achievement->id;
260
        });
261
262
        // Since we're dealing with owner-related criterias (progress exists if owner has any value),
263
        // we can simply count completed criterias & determine if achievement has been completed.
264
        $completedCriteriasCount = array_sum(
265
            array_map(function (AchievementCriteria $criteria) {
266
                return $criteria->completed() ? 1 : 0;
267
            }, $achievementCriterias)
268
        );
269
270
        return new Achievement([
271
            'id' => $achievement->id,
272
            'name' => $achievement->name,
273
            'description' => $achievement->description,
274
            'points' => $achievement->points,
275
            'completed' => count($achievementCriterias) === $completedCriteriasCount,
276
            'criterias' => $achievementCriterias,
277
        ]);
278
    }
279
280
    /**
281
     * Extracts single Achievement from given list for given criteria.
282
     *
283
     * @param AchievementCriteria $criteria
284
     * @param array               $achievements
285
     *
286
     * @return Achievement
287
     */
288
    public function getAchievementForCriteria(AchievementCriteria $criteria, array $achievements)
289
    {
290
        $collection = collect($achievements);
291
292
        $index = $collection->search(function (Achievement $achievement) use ($criteria) {
293
            return $achievement->id() === $criteria->achievementId();
294
        });
295
296
        if ($index === false) {
297
            throw new \InvalidArgumentException('Achievement for criteria #'.$criteria->id().' was not found.');
298
        }
299
300
        return $collection->get($index);
301
    }
302
303
    /**
304
     * Loads achievements with progresses for given owner.
305
     *
306
     * @param mixed $owner
307
     * @param array $achievementIds
308
     *
309
     * @return array
310
     */
311
    public function getAchievementsWithProgressFor($owner, array $achievementIds)
312
    {
313
        $criterias = $this->getCriteriasByAchievementIds($achievementIds);
314
315
        if (!count($criterias)) {
316
            return [];
317
        }
318
319
        $ownerCriteriaProgress = $this->getOwnerCriteriasProgress($owner, function ($query) use ($criterias) {
320
            $query->whereIn('achievement_criteria_model_id', $criterias->pluck('id'));
321
        });
322
323
        $achievementsCriterias = $criterias->map(function ($criteria) use ($ownerCriteriaProgress) {
324
            /** @var AchievementCriteriaModel $criteria */
325
326
            return $this->transformCriteriaWithProgress($criteria, $ownerCriteriaProgress->get($criteria->id));
327
        });
328
329
        return $this->getAchievementsByCriterias($achievementsCriterias->toArray());
330
    }
331
332
    /**
333
     * Loads criterias by achievement IDs.
334
     *
335
     * @param array $achievementIds
336
     *
337
     * @return \Illuminate\Database\Eloquent\Collection
338
     */
339
    public function getCriteriasByAchievementIds(array $achievementIds)
340
    {
341
        return $this->getModelClass(static::MODEL_CRITERIA, function (string $className) use ($achievementIds) {
342
            return $className::whereIn('achievement_id', $achievementIds)->get();
343
        });
344
    }
345
346
    /**
347
     * Saves criteria progress for given owner.
348
     *
349
     * @param mixed                       $owner
350
     * @param AchievementCriteria         $criteria
351
     * @param Achievement                 $achievement
352
     * @param AchievementCriteriaProgress $progress
353
     *
354
     * @return mixed
355
     */
356
    public function setCriteriaProgressUpdated($owner, AchievementCriteria $criteria, Achievement $achievement, AchievementCriteriaProgress $progress)
357
    {
358
        $owner->achievementCriterias()->syncWithoutDetaching([
359
            $criteria->id() => [
360
                'value' => $progress->value,
361
                'completed' => $progress->completed,
362
                'progress_data' => json_encode($progress->data),
363
            ],
364
        ]);
365
366
        Event::dispatch(new CriteriaUpdated($owner, $criteria, $achievement, $progress));
367
368
        return true;
369
    }
370
371
    /**
372
     * Saves given achievements completeness state for given owner.
373
     *
374
     * @param mixed $owner
375
     * @param array $achievements
376
     *
377
     * @return mixed
378
     */
379
    public function setAchievementsCompleted($owner, array $achievements)
380
    {
381
        $now = Carbon::now();
382
        $patch = [];
383
384
        foreach ($achievements as $achievement) {
385
            $patch[$achievement->id()] = ['completed_at' => $now];
386
        }
387
388
        $owner->achievements()->syncWithoutDetaching($patch);
389
390
        Event::dispatch(new AchievementsCompleted($owner, $achievements));
391
392
        return true;
393
    }
394
}
395