Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract local Model scopes to Eloquent Builder traits #227

Merged
merged 3 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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