Skip to content

Commit 1df2ed9

Browse files
authored
Extract local Model scopes to Eloquent Builder traits (#227)
1 parent 0c0036a commit 1df2ed9

File tree

14 files changed

+1813
-1480
lines changed

14 files changed

+1813
-1480
lines changed

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@ All notable changes to `laravel-love` will be documented in this file.
44

55
## [Unreleased]
66

7+
Code has breaking changes because of Eloquent Model local scopes refactoring.
8+
9+
Follow [upgrade instructions](UPGRADING.md#from-v8-to-v9) to migrate code.
10+
711
### Added
812

913
- ([#234]) Added Laravel 10 support
1014
- ([#216]) Added Docker Compose to quick development build
11-
- ([#188]) Added `whereReactedTo` model scope to Reacterable models
12-
- ([#188]) Added `whereNotReactedTo` model scope to Reacterable models
15+
- ([#227]) Added `whereReactedTo` model scope to Reacterable models
16+
- ([#227]) Added `whereNotReactedTo` model scope to Reacterable models
1317

1418
### Changed
1519

20+
- ([#227]) Extracted Reactable model scopes to `ReactableEloquentBuilderTrait`
1621
- ([#231]) Console command `love:setup-reactable` generates migration with nullable column `love_reactant_id` by default
1722
- ([#231]) Console command `love:setup-reactable` option `--nullable` replaced with `--not-nullable`
1823
- ([#231]) Console command `love:setup-reacterable` generates migration with nullable column `love_reacter_id` by default
@@ -572,6 +577,7 @@ Follow [upgrade instructions](UPGRADING.md#from-v5-to-v6) to migrate database to
572577
[#234]: https://github.com/cybercog/laravel-love/pull/234
573578
[#233]: https://github.com/cybercog/laravel-love/pull/233
574579
[#231]: https://github.com/cybercog/laravel-love/pull/231
580+
[#227]: https://github.com/cybercog/laravel-love/pull/227
575581
[#222]: https://github.com/cybercog/laravel-love/pull/222
576582
[#218]: https://github.com/cybercog/laravel-love/pull/218
577583
[#217]: https://github.com/cybercog/laravel-love/pull/217

UPGRADING.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,55 @@
11
# Upgrade Guide
22

3+
- [From v8 to v9](#from-v8-to-v9)
34
- [From v7 to v8](#from-v7-to-v8)
45
- [From v6 to v7](#from-v6-to-v7)
56
- [From v5 to v6](#from-v5-to-v6)
67
- [From v4 to v5](#from-v4-to-v5)
78
- [From v3 to v4](#from-v3-to-v4)
89

10+
## From v8 to v9
11+
12+
Release v9 has new Eloquent model local scopes approach described in ([#226](https://github.com/cybercog/laravel-love/discussions/226#discussioncomment-4612667)).
13+
14+
### Scopes breaking changes
15+
16+
Reactable trait methods `scopeWhereReactedBy`, `scopeWhereNotReactedBy`, `scopeJoinReactionCounterOfType`, `scopeJoinReactionTotal`
17+
were moved to `ReactableEloquentBuilderTrait`.
18+
19+
To start using them you have to create custom Eloquent Builder class, use trait in it and declare
20+
that it should be used in your model as default query builder in `newEloquentBuilder` method:
21+
22+
```php
23+
/**
24+
* @method static UserEloquentBuilder query()
25+
*/
26+
class User extends Model
27+
{
28+
public function newEloquentBuilder($query): UserEloquentBuilder
29+
{
30+
return new UserEloquentBuilder($query);
31+
}
32+
}
33+
34+
class UserEloquentBuilder extends \Illuminate\Database\Eloquent\Builder
35+
{
36+
use \Cog\Laravel\Love\Reactable\ReactableEloquentBuilderTrait;
37+
38+
// Other User model local query scopes
39+
}
40+
```
41+
42+
### Console commands breaking changes
43+
44+
Command `love:recount` does not use `sync` connection by default now.
45+
It uses `queue.default` config value.
46+
47+
To force run synchronous statistics recount use `--queue-connection=sync` option.
48+
49+
```shell
50+
php artisan love:recount --model="App\User" --queue-connection=sync
51+
```
52+
953
## From v7 to v8
1054

1155
- All `weight` values are `float` now. Round them to get `integer` values as it was before

src/Reactable/Models/Traits/Reactable.php

Lines changed: 0 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,11 @@
1616
use Cog\Contracts\Love\Reactable\Exceptions\AlreadyRegisteredAsLoveReactant;
1717
use Cog\Contracts\Love\Reactant\Facades\Reactant as ReactantFacadeInterface;
1818
use Cog\Contracts\Love\Reactant\Models\Reactant as ReactantInterface;
19-
use Cog\Contracts\Love\Reacterable\Models\Reacterable as ReacterableInterface;
2019
use Cog\Laravel\Love\Reactable\Observers\ReactableObserver;
2120
use Cog\Laravel\Love\Reactant\Facades\Reactant as ReactantFacade;
2221
use Cog\Laravel\Love\Reactant\Models\NullReactant;
2322
use Cog\Laravel\Love\Reactant\Models\Reactant;
24-
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter;
25-
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal;
26-
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
27-
use Illuminate\Database\Eloquent\Builder;
2823
use Illuminate\Database\Eloquent\Relations\BelongsTo;
29-
use Illuminate\Database\Query\JoinClause;
30-
use Illuminate\Support\Str;
3124

3225
/**
3326
* @mixin \Cog\Contracts\Love\Reactable\Models\Reactable
@@ -78,78 +71,4 @@ public function registerAsLoveReactant(): void
7871
$this->setAttribute('love_reactant_id', $reactant->getId());
7972
$this->save();
8073
}
81-
82-
public function scopeWhereReactedBy(
83-
Builder $query,
84-
ReacterableInterface $reacterable,
85-
?string $reactionTypeName = null,
86-
): Builder {
87-
return $query->whereHas(
88-
'loveReactant.reactions',
89-
function (Builder $reactionsQuery) use ($reacterable, $reactionTypeName) {
90-
$reactionsQuery->where('reacter_id', $reacterable->getLoveReacter()->getId());
91-
if ($reactionTypeName !== null) {
92-
$reactionsQuery->where('reaction_type_id', ReactionType::fromName($reactionTypeName)->getId());
93-
}
94-
}
95-
);
96-
}
97-
98-
public function scopeWhereNotReactedBy(
99-
Builder $query,
100-
ReacterableInterface $reacterable,
101-
?string $reactionTypeName = null,
102-
): Builder {
103-
return $query->whereDoesntHave(
104-
'loveReactant.reactions',
105-
function (Builder $reactionsQuery) use ($reacterable, $reactionTypeName) {
106-
$reactionsQuery->where('reacter_id', $reacterable->getLoveReacter()->getId());
107-
if ($reactionTypeName !== null) {
108-
$reactionsQuery->where('reaction_type_id', ReactionType::fromName($reactionTypeName)->getId());
109-
}
110-
}
111-
);
112-
}
113-
114-
public function scopeJoinReactionCounterOfType(
115-
Builder $query,
116-
string $reactionTypeName,
117-
?string $alias = null,
118-
): Builder {
119-
$reactionType = ReactionType::fromName($reactionTypeName);
120-
$alias = $alias === null ? 'reaction_' . Str::snake($reactionType->getName()) : $alias;
121-
122-
$select = $query->getQuery()->columns ?? ["{$this->getTable()}.*"];
123-
$select[] = $query->raw("COALESCE({$alias}.count, 0) as {$alias}_count");
124-
$select[] = $query->raw("COALESCE({$alias}.weight, 0) as {$alias}_weight");
125-
126-
return $query
127-
->leftJoin(
128-
(new ReactionCounter())->getTable() . ' as ' . $alias,
129-
function (JoinClause $join) use ($reactionType, $alias) {
130-
$join->on("{$alias}.reactant_id", '=', "{$this->getTable()}.love_reactant_id");
131-
$join->where("{$alias}.reaction_type_id", $reactionType->getId());
132-
}
133-
)
134-
->select($select);
135-
}
136-
137-
public function scopeJoinReactionTotal(
138-
Builder $query,
139-
?string $alias = null,
140-
): Builder {
141-
$alias = $alias === null ? 'reaction_total' : $alias;
142-
$select = $query->getQuery()->columns ?? ["{$this->getTable()}.*"];
143-
$select[] = $query->raw("COALESCE({$alias}.count, 0) as {$alias}_count");
144-
$select[] = $query->raw("COALESCE({$alias}.weight, 0) as {$alias}_weight");
145-
146-
return $query
147-
->leftJoin(
148-
(new ReactionTotal())->getTable() . ' as ' . $alias,
149-
"{$alias}.reactant_id",
150-
'=',
151-
"{$this->getTable()}.love_reactant_id"
152-
)
153-
->select($select);
154-
}
15574
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
3+
/*
4+
* This file is part of Laravel Love.
5+
*
6+
* (c) Anton Komarev <anton@komarev.com>
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\Reactable;
15+
16+
use Cog\Contracts\Love\Reacterable\Models\Reacterable as ReacterableInterface;
17+
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter;
18+
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal;
19+
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
20+
use Illuminate\Database\Eloquent\Builder;
21+
use Illuminate\Database\Query\JoinClause;
22+
use Illuminate\Support\Str;
23+
24+
/**
25+
* @mixin Builder
26+
*/
27+
trait ReactableEloquentBuilderTrait
28+
{
29+
public function whereReactedBy(
30+
ReacterableInterface $reacterable,
31+
?string $reactionTypeName = null,
32+
): Builder {
33+
return $this->whereHas(
34+
'loveReactant.reactions',
35+
function (Builder $reactionsQuery) use (
36+
$reacterable,
37+
$reactionTypeName,
38+
): void {
39+
$reactionsQuery->where(
40+
'reacter_id',
41+
$reacterable->getLoveReacter()->getId(),
42+
);
43+
44+
if ($reactionTypeName !== null) {
45+
$reactionsQuery->where(
46+
'reaction_type_id',
47+
ReactionType::fromName($reactionTypeName)->getId(),
48+
);
49+
}
50+
}
51+
);
52+
}
53+
54+
public function whereNotReactedBy(
55+
ReacterableInterface $reacterable,
56+
?string $reactionTypeName = null,
57+
): Builder {
58+
return $this->whereDoesntHave(
59+
'loveReactant.reactions',
60+
function (Builder $reactionsQuery) use (
61+
$reacterable,
62+
$reactionTypeName,
63+
): void {
64+
$reactionsQuery->where(
65+
'reacter_id',
66+
$reacterable->getLoveReacter()->getId(),
67+
);
68+
69+
if ($reactionTypeName !== null) {
70+
$reactionsQuery->where(
71+
'reaction_type_id',
72+
ReactionType::fromName($reactionTypeName)->getId(),
73+
);
74+
}
75+
}
76+
);
77+
}
78+
79+
public function joinReactionCounterOfType(
80+
string $reactionTypeName,
81+
?string $alias = null,
82+
): Builder {
83+
$reactionType = ReactionType::fromName($reactionTypeName);
84+
$alias = $alias === null
85+
? 'reaction_' . Str::snake($reactionType->getName())
86+
: $alias;
87+
88+
$select = $this->getQuery()->columns ?? ["{$this->getModel()->getTable()}.*"];
89+
$select[] = $this->raw("COALESCE({$alias}.count, 0) as {$alias}_count");
90+
$select[] = $this->raw("COALESCE({$alias}.weight, 0) as {$alias}_weight");
91+
92+
return $this
93+
->leftJoin(
94+
(new ReactionCounter())->getTable() . ' as ' . $alias,
95+
function (JoinClause $join) use (
96+
$reactionType,
97+
$alias,
98+
): void {
99+
$join->on(
100+
"{$alias}.reactant_id",
101+
'=',
102+
"{$this->getModel()->getTable()}.love_reactant_id",
103+
);
104+
$join->where(
105+
"{$alias}.reaction_type_id",
106+
$reactionType->getId(),
107+
);
108+
}
109+
)
110+
->select($select);
111+
}
112+
113+
public function joinReactionTotal(
114+
?string $alias = null,
115+
): Builder {
116+
$alias = $alias === null
117+
? 'reaction_total'
118+
: $alias;
119+
120+
$select = $this->getQuery()->columns ?? ["{$this->getModel()->getTable()}.*"];
121+
$select[] = $this->raw("COALESCE({$alias}.count, 0) as {$alias}_count");
122+
$select[] = $this->raw("COALESCE({$alias}.weight, 0) as {$alias}_weight");
123+
124+
return $this
125+
->leftJoin(
126+
(new ReactionTotal())->getTable() . ' as ' . $alias,
127+
"{$alias}.reactant_id",
128+
'=',
129+
"{$this->getModel()->getTable()}.love_reactant_id",
130+
)
131+
->select($select);
132+
}
133+
}

src/Reacterable/Models/Traits/Reacterable.php

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,13 @@
1313

1414
namespace Cog\Laravel\Love\Reacterable\Models\Traits;
1515

16-
use Cog\Contracts\Love\Reactable\Models\Reactable as ReactableInterface;
1716
use Cog\Contracts\Love\Reacter\Facades\Reacter as ReacterFacadeInterface;
1817
use Cog\Contracts\Love\Reacter\Models\Reacter as ReacterInterface;
1918
use Cog\Contracts\Love\Reacterable\Exceptions\AlreadyRegisteredAsLoveReacter;
2019
use Cog\Laravel\Love\Reacter\Facades\Reacter as ReacterFacade;
2120
use Cog\Laravel\Love\Reacter\Models\NullReacter;
2221
use Cog\Laravel\Love\Reacter\Models\Reacter;
2322
use Cog\Laravel\Love\Reacterable\Observers\ReacterableObserver;
24-
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
25-
use Illuminate\Database\Eloquent\Builder;
2623
use Illuminate\Database\Eloquent\Relations\BelongsTo;
2724

2825
/**
@@ -74,36 +71,4 @@ public function registerAsLoveReacter(): void
7471
$this->setAttribute('love_reacter_id', $reacter->getId());
7572
$this->save();
7673
}
77-
78-
public function scopeWhereReactedTo(
79-
Builder $query,
80-
ReactableInterface $reactable,
81-
?string $reactionTypeName = null,
82-
): Builder {
83-
return $query->whereHas(
84-
'loveReacter.reactions',
85-
function (Builder $reactionsQuery) use ($reactable, $reactionTypeName) {
86-
$reactionsQuery->where('reactant_id', $reactable->getLoveReactant()->getId());
87-
if ($reactionTypeName !== null) {
88-
$reactionsQuery->where('reaction_type_id', ReactionType::fromName($reactionTypeName)->getId());
89-
}
90-
}
91-
);
92-
}
93-
94-
public function scopeWhereNotReactedTo(
95-
Builder $query,
96-
ReactableInterface $reactable,
97-
?string $reactionTypeName = null,
98-
): Builder {
99-
return $query->whereDoesntHave(
100-
'loveReacter.reactions',
101-
function (Builder $reactionsQuery) use ($reactable, $reactionTypeName) {
102-
$reactionsQuery->where('reactant_id', $reactable->getLoveReactant()->getId());
103-
if ($reactionTypeName !== null) {
104-
$reactionsQuery->where('reaction_type_id', ReactionType::fromName($reactionTypeName)->getId());
105-
}
106-
}
107-
);
108-
}
10974
}

0 commit comments

Comments
 (0)