Skip to content

Commit

Permalink
Extract local Model scopes to Eloquent Builder traits (#227)
Browse files Browse the repository at this point in the history
  • Loading branch information
antonkomarev authored Feb 24, 2023
1 parent 0c0036a commit 1df2ed9
Show file tree
Hide file tree
Showing 14 changed files with 1,813 additions and 1,480 deletions.
10 changes: 8 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@ All notable changes to `laravel-love` will be documented in this file.

## [Unreleased]

Code has breaking changes because of Eloquent Model local scopes refactoring.

Follow [upgrade instructions](UPGRADING.md#from-v8-to-v9) to migrate code.

### Added

- ([#234]) Added Laravel 10 support
- ([#216]) Added Docker Compose to quick development build
- ([#188]) Added `whereReactedTo` model scope to Reacterable models
- ([#188]) Added `whereNotReactedTo` model scope to Reacterable models
- ([#227]) Added `whereReactedTo` model scope to Reacterable models
- ([#227]) Added `whereNotReactedTo` model scope to Reacterable models

### Changed

- ([#227]) Extracted Reactable model scopes to `ReactableEloquentBuilderTrait`
- ([#231]) Console command `love:setup-reactable` generates migration with nullable column `love_reactant_id` by default
- ([#231]) Console command `love:setup-reactable` option `--nullable` replaced with `--not-nullable`
- ([#231]) Console command `love:setup-reacterable` generates migration with nullable column `love_reacter_id` by default
Expand Down Expand Up @@ -572,6 +577,7 @@ Follow [upgrade instructions](UPGRADING.md#from-v5-to-v6) to migrate database to
[#234]: https://github.com/cybercog/laravel-love/pull/234
[#233]: https://github.com/cybercog/laravel-love/pull/233
[#231]: https://github.com/cybercog/laravel-love/pull/231
[#227]: https://github.com/cybercog/laravel-love/pull/227
[#222]: https://github.com/cybercog/laravel-love/pull/222
[#218]: https://github.com/cybercog/laravel-love/pull/218
[#217]: https://github.com/cybercog/laravel-love/pull/217
Expand Down
44 changes: 44 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,55 @@
# Upgrade Guide

- [From v8 to v9](#from-v8-to-v9)
- [From v7 to v8](#from-v7-to-v8)
- [From v6 to v7](#from-v6-to-v7)
- [From v5 to v6](#from-v5-to-v6)
- [From v4 to v5](#from-v4-to-v5)
- [From v3 to v4](#from-v3-to-v4)

## From v8 to v9

Release v9 has new Eloquent model local scopes approach described in ([#226](https://github.com/cybercog/laravel-love/discussions/226#discussioncomment-4612667)).

### Scopes breaking changes

Reactable trait methods `scopeWhereReactedBy`, `scopeWhereNotReactedBy`, `scopeJoinReactionCounterOfType`, `scopeJoinReactionTotal`
were moved to `ReactableEloquentBuilderTrait`.

To start using them you have to create custom Eloquent Builder class, use trait in it and declare
that it should be used in your model as default query builder in `newEloquentBuilder` method:

```php
/**
* @method static UserEloquentBuilder query()
*/
class User extends Model
{
public function newEloquentBuilder($query): UserEloquentBuilder
{
return new UserEloquentBuilder($query);
}
}

class UserEloquentBuilder extends \Illuminate\Database\Eloquent\Builder
{
use \Cog\Laravel\Love\Reactable\ReactableEloquentBuilderTrait;

// Other User model local query scopes
}
```

### Console commands breaking changes

Command `love:recount` does not use `sync` connection by default now.
It uses `queue.default` config value.

To force run synchronous statistics recount use `--queue-connection=sync` option.

```shell
php artisan love:recount --model="App\User" --queue-connection=sync
```

## From v7 to v8

- All `weight` values are `float` now. Round them to get `integer` values as it was before
Expand Down
81 changes: 0 additions & 81 deletions src/Reactable/Models/Traits/Reactable.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,11 @@
use Cog\Contracts\Love\Reactable\Exceptions\AlreadyRegisteredAsLoveReactant;
use Cog\Contracts\Love\Reactant\Facades\Reactant as ReactantFacadeInterface;
use Cog\Contracts\Love\Reactant\Models\Reactant as ReactantInterface;
use Cog\Contracts\Love\Reacterable\Models\Reacterable as ReacterableInterface;
use Cog\Laravel\Love\Reactable\Observers\ReactableObserver;
use Cog\Laravel\Love\Reactant\Facades\Reactant as ReactantFacade;
use Cog\Laravel\Love\Reactant\Models\NullReactant;
use Cog\Laravel\Love\Reactant\Models\Reactant;
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter;
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal;
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Str;

/**
* @mixin \Cog\Contracts\Love\Reactable\Models\Reactable
Expand Down Expand Up @@ -78,78 +71,4 @@ public function registerAsLoveReactant(): void
$this->setAttribute('love_reactant_id', $reactant->getId());
$this->save();
}

public function scopeWhereReactedBy(
Builder $query,
ReacterableInterface $reacterable,
?string $reactionTypeName = null,
): Builder {
return $query->whereHas(
'loveReactant.reactions',
function (Builder $reactionsQuery) use ($reacterable, $reactionTypeName) {
$reactionsQuery->where('reacter_id', $reacterable->getLoveReacter()->getId());
if ($reactionTypeName !== null) {
$reactionsQuery->where('reaction_type_id', ReactionType::fromName($reactionTypeName)->getId());
}
}
);
}

public function scopeWhereNotReactedBy(
Builder $query,
ReacterableInterface $reacterable,
?string $reactionTypeName = null,
): Builder {
return $query->whereDoesntHave(
'loveReactant.reactions',
function (Builder $reactionsQuery) use ($reacterable, $reactionTypeName) {
$reactionsQuery->where('reacter_id', $reacterable->getLoveReacter()->getId());
if ($reactionTypeName !== null) {
$reactionsQuery->where('reaction_type_id', ReactionType::fromName($reactionTypeName)->getId());
}
}
);
}

public function scopeJoinReactionCounterOfType(
Builder $query,
string $reactionTypeName,
?string $alias = null,
): Builder {
$reactionType = ReactionType::fromName($reactionTypeName);
$alias = $alias === null ? 'reaction_' . Str::snake($reactionType->getName()) : $alias;

$select = $query->getQuery()->columns ?? ["{$this->getTable()}.*"];
$select[] = $query->raw("COALESCE({$alias}.count, 0) as {$alias}_count");
$select[] = $query->raw("COALESCE({$alias}.weight, 0) as {$alias}_weight");

return $query
->leftJoin(
(new ReactionCounter())->getTable() . ' as ' . $alias,
function (JoinClause $join) use ($reactionType, $alias) {
$join->on("{$alias}.reactant_id", '=', "{$this->getTable()}.love_reactant_id");
$join->where("{$alias}.reaction_type_id", $reactionType->getId());
}
)
->select($select);
}

public function scopeJoinReactionTotal(
Builder $query,
?string $alias = null,
): Builder {
$alias = $alias === null ? 'reaction_total' : $alias;
$select = $query->getQuery()->columns ?? ["{$this->getTable()}.*"];
$select[] = $query->raw("COALESCE({$alias}.count, 0) as {$alias}_count");
$select[] = $query->raw("COALESCE({$alias}.weight, 0) as {$alias}_weight");

return $query
->leftJoin(
(new ReactionTotal())->getTable() . ' as ' . $alias,
"{$alias}.reactant_id",
'=',
"{$this->getTable()}.love_reactant_id"
)
->select($select);
}
}
133 changes: 133 additions & 0 deletions src/Reactable/ReactableEloquentBuilderTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<?php

/*
* This file is part of Laravel Love.
*
* (c) Anton Komarev <anton@komarev.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Cog\Laravel\Love\Reactable;

use Cog\Contracts\Love\Reacterable\Models\Reacterable as ReacterableInterface;
use Cog\Laravel\Love\Reactant\ReactionCounter\Models\ReactionCounter;
use Cog\Laravel\Love\Reactant\ReactionTotal\Models\ReactionTotal;
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Str;

/**
* @mixin Builder
*/
trait ReactableEloquentBuilderTrait
{
public function whereReactedBy(
ReacterableInterface $reacterable,
?string $reactionTypeName = null,
): Builder {
return $this->whereHas(
'loveReactant.reactions',
function (Builder $reactionsQuery) use (
$reacterable,
$reactionTypeName,
): void {
$reactionsQuery->where(
'reacter_id',
$reacterable->getLoveReacter()->getId(),
);

if ($reactionTypeName !== null) {
$reactionsQuery->where(
'reaction_type_id',
ReactionType::fromName($reactionTypeName)->getId(),
);
}
}
);
}

public function whereNotReactedBy(
ReacterableInterface $reacterable,
?string $reactionTypeName = null,
): Builder {
return $this->whereDoesntHave(
'loveReactant.reactions',
function (Builder $reactionsQuery) use (
$reacterable,
$reactionTypeName,
): void {
$reactionsQuery->where(
'reacter_id',
$reacterable->getLoveReacter()->getId(),
);

if ($reactionTypeName !== null) {
$reactionsQuery->where(
'reaction_type_id',
ReactionType::fromName($reactionTypeName)->getId(),
);
}
}
);
}

public function joinReactionCounterOfType(
string $reactionTypeName,
?string $alias = null,
): Builder {
$reactionType = ReactionType::fromName($reactionTypeName);
$alias = $alias === null
? 'reaction_' . Str::snake($reactionType->getName())
: $alias;

$select = $this->getQuery()->columns ?? ["{$this->getModel()->getTable()}.*"];
$select[] = $this->raw("COALESCE({$alias}.count, 0) as {$alias}_count");
$select[] = $this->raw("COALESCE({$alias}.weight, 0) as {$alias}_weight");

return $this
->leftJoin(
(new ReactionCounter())->getTable() . ' as ' . $alias,
function (JoinClause $join) use (
$reactionType,
$alias,
): void {
$join->on(
"{$alias}.reactant_id",
'=',
"{$this->getModel()->getTable()}.love_reactant_id",
);
$join->where(
"{$alias}.reaction_type_id",
$reactionType->getId(),
);
}
)
->select($select);
}

public function joinReactionTotal(
?string $alias = null,
): Builder {
$alias = $alias === null
? 'reaction_total'
: $alias;

$select = $this->getQuery()->columns ?? ["{$this->getModel()->getTable()}.*"];
$select[] = $this->raw("COALESCE({$alias}.count, 0) as {$alias}_count");
$select[] = $this->raw("COALESCE({$alias}.weight, 0) as {$alias}_weight");

return $this
->leftJoin(
(new ReactionTotal())->getTable() . ' as ' . $alias,
"{$alias}.reactant_id",
'=',
"{$this->getModel()->getTable()}.love_reactant_id",
)
->select($select);
}
}
35 changes: 0 additions & 35 deletions src/Reacterable/Models/Traits/Reacterable.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@

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

use Cog\Contracts\Love\Reactable\Models\Reactable as ReactableInterface;
use Cog\Contracts\Love\Reacter\Facades\Reacter as ReacterFacadeInterface;
use Cog\Contracts\Love\Reacter\Models\Reacter as ReacterInterface;
use Cog\Contracts\Love\Reacterable\Exceptions\AlreadyRegisteredAsLoveReacter;
use Cog\Laravel\Love\Reacter\Facades\Reacter as ReacterFacade;
use Cog\Laravel\Love\Reacter\Models\NullReacter;
use Cog\Laravel\Love\Reacter\Models\Reacter;
use Cog\Laravel\Love\Reacterable\Observers\ReacterableObserver;
use Cog\Laravel\Love\ReactionType\Models\ReactionType;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

/**
Expand Down Expand Up @@ -74,36 +71,4 @@ public function registerAsLoveReacter(): void
$this->setAttribute('love_reacter_id', $reacter->getId());
$this->save();
}

public function scopeWhereReactedTo(
Builder $query,
ReactableInterface $reactable,
?string $reactionTypeName = null,
): Builder {
return $query->whereHas(
'loveReacter.reactions',
function (Builder $reactionsQuery) use ($reactable, $reactionTypeName) {
$reactionsQuery->where('reactant_id', $reactable->getLoveReactant()->getId());
if ($reactionTypeName !== null) {
$reactionsQuery->where('reaction_type_id', ReactionType::fromName($reactionTypeName)->getId());
}
}
);
}

public function scopeWhereNotReactedTo(
Builder $query,
ReactableInterface $reactable,
?string $reactionTypeName = null,
): Builder {
return $query->whereDoesntHave(
'loveReacter.reactions',
function (Builder $reactionsQuery) use ($reactable, $reactionTypeName) {
$reactionsQuery->where('reactant_id', $reactable->getLoveReactant()->getId());
if ($reactionTypeName !== null) {
$reactionsQuery->where('reaction_type_id', ReactionType::fromName($reactionTypeName)->getId());
}
}
);
}
}
Loading

0 comments on commit 1df2ed9

Please sign in to comment.