Passed
Push — master ( f75c84...6aec09 )
by Anton
02:54
created

src/Reactant/Models/Reactant.php (1 issue)

Labels
Severity
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 ReactableContract;
17
use Cog\Contracts\Love\Reactant\Exceptions\NotAssignedToReactable;
18
use Cog\Contracts\Love\Reactant\Models\Reactant as ReactantContract;
19
use Cog\Contracts\Love\Reactant\ReactionCounter\Exceptions\ReactionCounterDuplicate;
20
use Cog\Contracts\Love\Reactant\ReactionCounter\Models\ReactionCounter as ReactionCounterContract;
21
use Cog\Contracts\Love\Reactant\ReactionTotal\Exceptions\ReactionTotalDuplicate;
22
use Cog\Contracts\Love\Reactant\ReactionTotal\Models\ReactionTotal as ReactionTotalContract;
23
use Cog\Contracts\Love\Reacter\Models\Reacter as ReacterContract;
24
use Cog\Contracts\Love\Reaction\Models\Reaction as ReactionContract;
25
use Cog\Contracts\Love\ReactionType\Models\ReactionType as ReactionTypeContract;
26
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\NullReactionCounter;
27
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter;
28
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\NullReactionTotal;
29
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal;
30
use Cog\Laravel\Love\Reaction\Models\Reaction;
31
use Cog\Laravel\Love\Support\Database\Eloquent\Model;
32
use Illuminate\Database\Eloquent\Relations\HasMany;
33
use Illuminate\Database\Eloquent\Relations\HasOne;
34
use Illuminate\Database\Eloquent\Relations\MorphTo;
35
36
final class Reactant extends Model implements
37
    ReactantContract
38
{
39
    protected $table = 'love_reactants';
40
41
    protected $fillable = [
42
        'type',
43
    ];
44
45
    protected $casts = [
46
        'id' => 'string',
47
    ];
48
49
    public function reactable(): MorphTo
50
    {
51
        return $this->morphTo('reactable', 'type', 'id', 'love_reactant_id');
52
    }
53
54
    public function reactions(): HasMany
55
    {
56
        return $this->hasMany(Reaction::class, 'reactant_id');
57
    }
58
59
    public function reactionCounters(): HasMany
60
    {
61
        return $this->hasMany(ReactionCounter::class, 'reactant_id');
62
    }
63
64
    public function reactionTotal(): HasOne
65
    {
66
        return $this->hasOne(ReactionTotal::class, 'reactant_id');
67
    }
68
69
    public function getId(): string
70
    {
71
        return $this->getAttributeValue('id');
72
    }
73
74
    public function getReactable(): ReactableContract
75
    {
76
        $reactable = $this->getAttribute('reactable');
0 ignored issues
show
The attribute reactable does not seem to exist on Cog\Laravel\Love\Reactant\Models\Reactant. Are you maybe missing a database migration?

If used attribute cannot be found there are two main reasons: 1. there is a creating database migration missing or 2. there is a major typo in either the declaration or the usage.

Loading history...
77
78
        if (is_null($reactable)) {
79
            throw new NotAssignedToReactable();
80
        }
81
82
        return $reactable;
83
    }
84
85
    public function getReactions(): iterable
86
    {
87
        return $this->getAttribute('reactions');
88
    }
89
90
    public function getReactionCounters(): iterable
91
    {
92
        return $this->getAttribute('reactionCounters');
93
    }
94
95
    public function getReactionCounterOfType(
96
        ReactionTypeContract $reactionType
97
    ): ReactionCounterContract {
98
        // TODO: Test query count with eager loaded relation
99
        // TODO: Test query count without eager loaded relation
100
        $counter = $this
101
            ->getAttribute('reactionCounters')
102
            ->where('reaction_type_id', $reactionType->getId())
103
            ->first();
104
105
        if (is_null($counter)) {
106
            return new NullReactionCounter($this, $reactionType);
107
        }
108
109
        return $counter;
110
    }
111
112
    public function getReactionTotal(): ReactionTotalContract
113
    {
114
        return $this->getAttribute('reactionTotal')
115
            ?? new NullReactionTotal($this);
116
    }
117
118
    public function isReactedBy(
119
        ReacterContract $reacter,
120
        ?ReactionTypeContract $reactionType = null,
121
        ?float $rate = null
122
    ): bool {
123
        if ($reacter->isNull()) {
124
            return false;
125
        }
126
127
        // TODO: Test if relation was loaded partially
128
        if ($this->relationLoaded('reactions')) {
129
            return $this
130
                ->getAttribute('reactions')
131
                ->contains(function (ReactionContract $reaction) use ($reacter, $reactionType, $rate) {
132
                    if ($reaction->isNotByReacter($reacter)) {
133
                        return false;
134
                    }
135
136
                    if (!is_null($reactionType) && $reaction->isNotOfType($reactionType)) {
137
                        return false;
138
                    }
139
140
                    if (!is_null($rate) && $reaction->getRate() !== $rate) {
141
                        return false;
142
                    }
143
144
                    return true;
145
                });
146
        }
147
148
        $query = $this->reactions()->where('reacter_id', $reacter->getId());
149
150
        if (!is_null($reactionType)) {
151
            $query->where('reaction_type_id', $reactionType->getId());
152
        }
153
154
        if (!is_null($rate)) {
155
            $query->where('rate', $rate);
156
        }
157
158
        return $query->exists();
159
    }
160
161
    public function isNotReactedBy(
162
        ReacterContract $reacter,
163
        ?ReactionTypeContract $reactionType = null,
164
        ?float $rate = null
165
    ): bool {
166
        return !$this->isReactedBy($reacter, $reactionType, $rate);
167
    }
168
169
    public function isEqualTo(
170
        ReactantContract $that
171
    ): bool {
172
        return $that->isNotNull()
173
            && $this->getId() === $that->getId();
174
    }
175
176
    public function isNotEqualTo(
177
        ReactantContract $that
178
    ): bool {
179
        return !$this->isEqualTo($that);
180
    }
181
182
    public function isNull(): bool
183
    {
184
        return !$this->exists;
185
    }
186
187
    public function isNotNull(): bool
188
    {
189
        return $this->exists;
190
    }
191
192
    public function createReactionCounterOfType(
193
        ReactionTypeContract $reactionType
194
    ): void {
195
        if ($this->reactionCounters()->where('reaction_type_id', $reactionType->getId())->exists()) {
196
            throw ReactionCounterDuplicate::ofTypeForReactant($reactionType, $this);
197
        }
198
199
        $this->reactionCounters()->create([
200
            'reaction_type_id' => $reactionType->getId(),
201
        ]);
202
203
        // Need to reload relation with fresh data
204
        $this->load('reactionCounters');
205
    }
206
207
    public function createReactionTotal(): void
208
    {
209
        if ($this->reactionTotal()->exists()) {
210
            throw ReactionTotalDuplicate::forReactant($this);
211
        }
212
213
        $this->reactionTotal()->create();
214
215
        // Need to reload relation with fresh data
216
        $this->load('reactionTotal');
217
    }
218
}
219