Passed
Push — master ( 497393...f8a28c )
by Anton
03:12 queued 16s
created

Reactant::getReactionsBy()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
c 0
b 0
f 0
dl 0
loc 15
rs 10
cc 3
nc 3
nop 1
1
<?php
2
3
/*
4
 * This file is part of Laravel Love.
5
 *
6
 * (c) Anton Komarev <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Cog\Laravel\Love\Reactant\Models;
15
16
use Cog\Contracts\Love\Reactable\Models\Reactable as ReactableInterface;
17
use Cog\Contracts\Love\Reactant\Exceptions\NotAssignedToReactable;
18
use Cog\Contracts\Love\Reactant\Models\Reactant as ReactantInterface;
19
use Cog\Contracts\Love\Reactant\ReactionCounter\Exceptions\ReactionCounterDuplicate;
20
use Cog\Contracts\Love\Reactant\ReactionCounter\Models\ReactionCounter as ReactionCounterInterface;
21
use Cog\Contracts\Love\Reactant\ReactionTotal\Exceptions\ReactionTotalDuplicate;
22
use Cog\Contracts\Love\Reactant\ReactionTotal\Models\ReactionTotal as ReactionTotalInterface;
23
use Cog\Contracts\Love\Reacter\Models\Reacter;
24
use Cog\Contracts\Love\Reacter\Models\Reacter as ReacterInterface;
25
use Cog\Contracts\Love\Reaction\Models\Reaction as ReactionInterface;
26
use Cog\Contracts\Love\ReactionType\Models\ReactionType as ReactionTypeInterface;
27
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\NullReactionCounter;
28
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter;
29
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\NullReactionTotal;
30
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal;
31
use Cog\Laravel\Love\Reaction\Models\Reaction;
32
use Cog\Laravel\Love\Support\Database\Eloquent\Model;
33
use Illuminate\Database\Eloquent\Factories\HasFactory;
34
use Illuminate\Database\Eloquent\Relations\HasMany;
35
use Illuminate\Database\Eloquent\Relations\HasOne;
36
use Illuminate\Database\Eloquent\Relations\MorphTo;
37
use Illuminate\Support\Collection;
38
39
final class Reactant extends Model implements
40
    ReactantInterface
41
{
42
    use HasFactory;
43
44
    protected $table = 'love_reactants';
45
46
    /**
47
     * @var string[]
48
     */
49
    protected $fillable = [
50
        'type',
51
    ];
52
53
    /**
54
     * @var string[]
55
     */
56
    protected $casts = [
57
        'id' => 'string',
58
    ];
59
60
    public function reactable(): MorphTo
61
    {
62
        return $this->morphTo('reactable', 'type', 'id', 'love_reactant_id');
63
    }
64
65
    public function reactions(): HasMany
66
    {
67
        return $this->hasMany(Reaction::class, 'reactant_id');
68
    }
69
70
    public function reactionCounters(): HasMany
71
    {
72
        return $this->hasMany(ReactionCounter::class, 'reactant_id');
73
    }
74
75
    public function reactionTotal(): HasOne
76
    {
77
        return $this->hasOne(ReactionTotal::class, 'reactant_id');
78
    }
79
80
    public function getId(): string
81
    {
82
        return $this->getAttributeValue('id');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAttributeValue('id') could return the type boolean|null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
83
    }
84
85
    public function getReactable(): ReactableInterface
86
    {
87
        $reactable = $this->getAttribute('reactable');
88
89
        if ($reactable === null) {
90
            throw new NotAssignedToReactable();
91
        }
92
93
        return $reactable;
94
    }
95
96
    public function getReactions(): iterable
97
    {
98
        return $this->getAttribute('reactions');
99
    }
100
101
    /**
102
     * @return iterable|\Cog\Contracts\Love\Reaction\Models\Reaction[]
103
     */
104
    public function getReactionsBy(
105
        Reacter $reacter,
106
    ): iterable {
107
        if ($reacter->isNull()) {
108
            return new Collection();
0 ignored issues
show
Bug Best Practice introduced by
The expression return new Illuminate\Support\Collection() returns the type Illuminate\Support\Collection which is incompatible with the documented return type Cog\Contracts\Love\React...els\Reaction[]|iterable.
Loading history...
109
        }
110
111
        // TODO: Test if relation was loaded partially
112
        if ($this->relationLoaded('reactions')) {
113
            return $this
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getAttribu...ion(...) { /* ... */ }) returns the type boolean which is incompatible with the type-hinted return iterable.
Loading history...
114
                ->getAttribute('reactions')
115
                ->contains(fn (ReactionInterface $reaction) => $reaction->isByReacter($reacter));
116
        }
117
118
        return $this->reactions()->where('reacter_id', $reacter->getId())->get();
119
    }
120
121
    public function getReactionCounters(): iterable
122
    {
123
        return $this->getAttribute('reactionCounters');
124
    }
125
126
    public function getReactionCounterOfType(
127
        ReactionTypeInterface $reactionType,
128
    ): ReactionCounterInterface {
129
        // TODO: Test query count with eager loaded relation
130
        // TODO: Test query count without eager loaded relation
131
        $counter = $this
132
            ->getAttribute('reactionCounters')
133
            ->where('reaction_type_id', $reactionType->getId())
134
            ->first();
135
136
        if ($counter === null) {
137
            return new NullReactionCounter($this, $reactionType);
138
        }
139
140
        return $counter;
141
    }
142
143
    public function getReactionTotal(): ReactionTotalInterface
144
    {
145
        return $this->getAttribute('reactionTotal')
146
            ?? new NullReactionTotal($this);
147
    }
148
149
    public function isReactedBy(
150
        ReacterInterface $reacter,
151
        ReactionTypeInterface | null $reactionType = null,
152
        float | null $rate = null,
153
    ): bool {
154
        if ($reacter->isNull()) {
155
            return false;
156
        }
157
158
        // TODO: Test if relation was loaded partially
159
        if ($this->relationLoaded('reactions')) {
160
            return $this
161
                ->getAttribute('reactions')
162
                ->contains(function (ReactionInterface $reaction) use ($reacter, $reactionType, $rate) {
163
                    if ($reaction->isNotByReacter($reacter)) {
164
                        return false;
165
                    }
166
167
                    if ($reactionType !== null && $reaction->isNotOfType($reactionType)) {
168
                        return false;
169
                    }
170
171
                    if ($rate !== null && $reaction->getRate() !== $rate) {
172
                        return false;
173
                    }
174
175
                    return true;
176
                });
177
        }
178
179
        $query = $this->reactions()->where('reacter_id', $reacter->getId());
180
181
        if ($reactionType !== null) {
182
            $query->where('reaction_type_id', $reactionType->getId());
183
        }
184
185
        if ($rate !== null) {
186
            $query->where('rate', $rate);
187
        }
188
189
        return $query->exists();
190
    }
191
192
    public function isNotReactedBy(
193
        ReacterInterface $reacter,
194
        ReactionTypeInterface | null $reactionType = null,
195
        float | null $rate = null,
196
    ): bool {
197
        return !$this->isReactedBy($reacter, $reactionType, $rate);
198
    }
199
200
    public function isEqualTo(
201
        ReactantInterface $that,
202
    ): bool {
203
        return $that->isNotNull()
204
            && $this->getId() === $that->getId();
205
    }
206
207
    public function isNotEqualTo(
208
        ReactantInterface $that,
209
    ): bool {
210
        return !$this->isEqualTo($that);
211
    }
212
213
    public function isNull(): bool
214
    {
215
        return !$this->exists;
216
    }
217
218
    public function isNotNull(): bool
219
    {
220
        return $this->exists;
221
    }
222
223
    public function createReactionCounterOfType(
224
        ReactionTypeInterface $reactionType,
225
    ): void {
226
        if ($this->reactionCounters()->where('reaction_type_id', $reactionType->getId())->exists()) {
227
            throw ReactionCounterDuplicate::ofTypeForReactant($reactionType, $this);
228
        }
229
230
        $this->reactionCounters()->create([
231
            'reaction_type_id' => $reactionType->getId(),
232
        ]);
233
234
        // Need to reload relation with fresh data
235
        $this->load('reactionCounters');
236
    }
237
238
    public function createReactionTotal(): void
239
    {
240
        if ($this->reactionTotal()->exists()) {
241
            throw ReactionTotalDuplicate::forReactant($this);
242
        }
243
244
        $this->reactionTotal()->create();
245
246
        // Need to reload relation with fresh data
247
        $this->load('reactionTotal');
248
    }
249
}
250