Skip to content

Commit

Permalink
added overtrue/laravel-like package
Browse files Browse the repository at this point in the history
  • Loading branch information
mdobydullah committed Mar 25, 2023
1 parent bee3c8d commit a1be948
Show file tree
Hide file tree
Showing 10 changed files with 339 additions and 7 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ php artisan vendor:publish --provider="Obydul\\Larable\\LarableServiceProvider"

The available morphable packages/features.

| Service Name | Feature/Package | Description |
|--------------|-----------------------------------------------------------------------------|-------------------------------------------------|
| Subscribe | [overtrue/laravel-subscribe](https://github.com/overtrue/laravel-subscribe) | User subscribe unsubscribe feature for Laravel. |
| Follow | [overtrue/laravel-follow](https://github.com/overtrue/laravel-follow) | User follow unfollow feature for Laravel. |
| Service Name | Feature/Package | Description |
|--------------|-----------------------------------------------------------------------------|-------------------------------------|
| Like | [overtrue/laravel-like](https://github.com/overtrue/laravel-like) | User like feature feature. |
| Follow | [overtrue/laravel-follow](https://github.com/overtrue/laravel-follow) | User follow unfollow feature . |
| Subscribe | [overtrue/laravel-subscribe](https://github.com/overtrue/laravel-subscribe) | User subscribe unsubscribe feature. |

## Cleanup Unused Services

Expand All @@ -50,14 +51,14 @@ Currently, there are 2 services. We will keep adding more services. The chances
},
"extra": {
"obydul/larable": [
"Subscribe",
"Like",
"Follow"
]
}
}
```

This example will remove all services other than "Subscribe" and "Follow" when ```composer update``` or a fresh ```composer install``` is run.
This example will remove all services other than "Like" and "Follow" when ```composer update``` or a fresh ```composer install``` is run.

**IMPORTANT**: If you add any services back in ```composer.json```, you will need to remove the ```vendor/obydul/larable``` directory explicitly for the change you made to have effect:

Expand Down
3 changes: 2 additions & 1 deletion src/Helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ public function services(): array
{
// all services
$all_services = [
'Subscribe',
'Like',
'Follow',
'Subscribe',
];

// read root composer.json
Expand Down
21 changes: 21 additions & 0 deletions src/Like/Events/Event.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Obydul\Larable\Like\Events;

use Illuminate\Database\Eloquent\Model;

class Event
{
/**
* @var \Illuminate\Database\Eloquent\Model
*/
public $like;

/**
* Event constructor.
*/
public function __construct(Model $like)
{
$this->like = $like;
}
}
7 changes: 7 additions & 0 deletions src/Like/Events/Liked.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Obydul\Larable\Like\Events;

class Liked extends Event
{
}
7 changes: 7 additions & 0 deletions src/Like/Events/Unliked.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Obydul\Larable\Like\Events;

class Unliked extends Event
{
}
69 changes: 69 additions & 0 deletions src/Like/Like.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Obydul\Larable\Like;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use Obydul\Larable\Like\Events\Liked;
use Obydul\Larable\Like\Events\Unliked;

class Like extends Model
{
protected $guarded = [];

protected $dispatchesEvents = [
'created' => Liked::class,
'deleted' => Unliked::class,
];

public function __construct(array $attributes = [])
{
$this->table = \config('larable_like.likes_table');

parent::__construct($attributes);
}

protected static function boot()
{
parent::boot();

self::saving(function ($like) {
$userForeignKey = \config('larable_like.user_foreign_key');
$like->{$userForeignKey} = $like->{$userForeignKey} ?: auth()->id();

if (\config('larable_like.uuids')) {
$like->{$like->getKeyName()} = $like->{$like->getKeyName()} ?: (string) Str::orderedUuid();
}
});
}

public function likeable(): \Illuminate\Database\Eloquent\Relations\MorphTo
{
return $this->morphTo();
}

/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(\config('auth.providers.users.model'), \config('larable_like.user_foreign_key'));
}

/**
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function liker()
{
return $this->user();
}

/**
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeWithType(Builder $query, string $type)
{
return $query->where('likeable_type', app($type)->getMorphClass());
}
}
29 changes: 29 additions & 0 deletions src/Like/Migrations/2023_03_25_000000_create_likes_table.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateLikesTable extends Migration
{
/**
* Run the migrations.
*/
public function up()
{
Schema::create(config('larable_like.likes_table', 'larable_likes'), function (Blueprint $table) {
$table->bigIncrements('id');
$table->unsignedBigInteger(config('larable_like.user_foreign_key'))->index()->comment('user_id');
$table->morphs('likeable');
$table->timestamps();
});
}

/**
* Reverse the migrations.
*/
public function down()
{
Schema::dropIfExists(config('larable_like.likes_table'));
}
}
35 changes: 35 additions & 0 deletions src/Like/Traits/Likeable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Obydul\Larable\Like\Traits;

use Illuminate\Database\Eloquent\Model;

trait Likeable
{
public function isLikedBy(Model $user): bool
{
if (\is_a($user, config('auth.providers.users.model'))) {
if ($this->relationLoaded('likers')) {
return $this->likers->contains($user);
}

return $this->likers()->where(\config('larable_like.user_foreign_key'), $user->getKey())->exists();
}

return false;
}

/**
* Return followers.
*/
public function likers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(
config('auth.providers.users.model'),
config('larable_like.likes_table'),
'likeable_id',
config('larable_like.user_foreign_key')
)
->where('likeable_type', $this->getMorphClass());
}
}
139 changes: 139 additions & 0 deletions src/Like/Traits/Liker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

namespace Obydul\Larable\Like\Traits;

use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Pagination\AbstractCursorPaginator;
use Illuminate\Pagination\AbstractPaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\LazyCollection;
use Obydul\Larable\Like\Like;

trait Liker
{
public function like(Model $object): Like
{
$attributes = [
'likeable_type' => $object->getMorphClass(),
'likeable_id' => $object->getKey(),
config('larable_like.user_foreign_key') => $this->getKey(),
];

/* @var \Illuminate\Database\Eloquent\Model $like */
$like = \app(config('larable_like.like_model'));

/* @var \Overtrue\LaravelLike\Traits\Likeable|\Illuminate\Database\Eloquent\Model $object */
return $like->where($attributes)->firstOr(
function () use ($like, $attributes) {
return $like->unguarded(function () use ($like, $attributes) {
if ($this->relationLoaded('likes')) {
$this->unsetRelation('likes');
}

return $like->create($attributes);
});
}
);
}

/**
* @throws \Exception
*/
public function unlike(Model $object): bool
{
/* @var \Overtrue\LaravelLike\Like $relation */
$relation = \app(config('larable_like.like_model'))
->where('likeable_id', $object->getKey())
->where('likeable_type', $object->getMorphClass())
->where(config('larable_like.user_foreign_key'), $this->getKey())
->first();

if ($relation) {
if ($this->relationLoaded('likes')) {
$this->unsetRelation('likes');
}

return $relation->delete();
}

return true;
}

/**
* @return Like|null
*
* @throws \Exception
*/
public function toggleLike(Model $object)
{
return $this->hasLiked($object) ? $this->unlike($object) : $this->like($object);
}

public function hasLiked(Model $object): bool
{
return ($this->relationLoaded('likes') ? $this->likes : $this->likes())
->where('likeable_id', $object->getKey())
->where('likeable_type', $object->getMorphClass())
->count() > 0;
}

public function likes(): HasMany
{
return $this->hasMany(config('larable_like.like_model'), config('larable_like.user_foreign_key'), $this->getKeyName());
}

/**
* Get Query Builder for likes
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function getLikedItems(string $model)
{
return app($model)->whereHas(
'likers',
function ($q) {
return $q->where(config('larable_like.user_foreign_key'), $this->getKey());
}
);
}

public function attachLikeStatus(&$likeables, callable $resolver = null)
{
$likes = $this->likes()->get()->keyBy(function ($item) {
return \sprintf('%s:%s', $item->likeable_type, $item->likeable_id);
});

$attachStatus = function ($likeable) use ($likes, $resolver) {
$resolver = $resolver ?? fn ($m) => $m;
$likeable = $resolver($likeable);

if ($likeable && \in_array(Likeable::class, \class_uses_recursive($likeable))) {
$key = \sprintf('%s:%s', $likeable->getMorphClass(), $likeable->getKey());
$likeable->setAttribute('has_liked', $likes->has($key));
}

return $likeable;
};

switch (true) {
case $likeables instanceof Model:
return $attachStatus($likeables);
case $likeables instanceof Collection:
return $likeables->each($attachStatus);
case $likeables instanceof LazyCollection:
return $likeables = $likeables->map($attachStatus);
case $likeables instanceof AbstractPaginator:
case $likeables instanceof AbstractCursorPaginator:
return $likeables->through($attachStatus);
case $likeables instanceof Paginator:
// custom paginator will return a collection
return collect($likeables->items())->transform($attachStatus);
case \is_array($likeables):
return \collect($likeables)->transform($attachStatus);
default:
throw new \InvalidArgumentException('Invalid argument type.');
}
}
}
23 changes: 23 additions & 0 deletions src/Like/config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

return [
/**
* Use uuid as primary key.
*/
'uuids' => false,

/*
* User tables foreign key name.
*/
'user_foreign_key' => 'user_id',

/*
* Table name for likes records.
*/
'likes_table' => 'larable_likes',

/*
* Model name for like record.
*/
'like_model' => \Obydul\Larable\Like\Like::class,
];

0 comments on commit a1be948

Please sign in to comment.