DARK MODE 

Posted on Thursday, August 4, 2022 by

Create YouTube Clone with Yii (Part 11): Implementing Like/Dislike Buttons

 1) Create video_like table, migration and model

php yii migrate/create create_video_like_table --fields="video_id:string(16):notNull:foreignKey(video),user_id:integer(11):notNull:foreignKey(user),type:integer(1),created_at:integer(11)"

php yii migrate



2) Save like counts

 5
 6
 7
..
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
use yii\web\Controller;
use common\models\Video;
use common\models\VideoLike;
..
    public function actionView($id)
    {
        $this->layout = 'blank';
        $video = $this->findVideo($id);

        // Save video views
        $videoView = new VideoView();
        $videoView->video_id = $id;
        $videoView->user_id = \Yii::$app->user->id;
        $videoView->created_at = time();
        $videoView->save();

        // Display video/view.php
        return $this->render('view', [
            'model' => $video
        ]);
    }
    
    public function actionLike($id)
    {
        $video = $this->findVideo($id);
        $userId = \Yii::$app->user->id;

        $videoLike = new VideoLike();
        $videoLike->video_id = $id;
        $videoLike->user_id = $userId;
        $videoLike->created_at = time();
        $videoLike->save();
    }

    public function findVideo($id)
    {
        $video = Video::findOne($id);

        // Display error if video id does not exist.
        if (!$video) {
            throw new NotFoundHttpException("Video does not exist.");
        }

        return $video;
    }
frontend > controllers > VideoController.php

 3
 4
..
19
20
21
22
23
24
25
26
27
28
29
use yii\helpers\Url;
use yii\widgets\Pjax;
...
            <div>
                <?php Pjax::begin() ?>
                <a href="<?php echo Url::to(['/video/like', 'id' => $model->video_id]) ?>"
                    class="btn btn-sm btn-outline-primary" data-method="post" data-pjax="1">
                    <i class="fa-solid fa-thumbs-up"></i> 9
                </a>
                <button class="btn btn-sm btn-outline-secondary">
                    <i class="fa-regular fa-thumbs-down"></i> 3
                </button>
                <?php Pjax::end() ?>
            </div>
frontend > views > video > view.php

When the like button is clicked, there should be a new data in the table video_like.



3) Redirect unauthorized user and accept button click as POST method

 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
93
use yii\web\Controller;
use common\models\Video;
use common\models\VideoLike;
use common\models\VideoView;
use yii\data\ActiveDataProvider;
use yii\filters\AccessControl;
use yii\filters\VerbFilter;
use yii\web\NotFoundHttpException;

class VideoController extends Controller
{
    public function behaviors()
    {
        return [
            // If unathorized user clicked the Like button, they will be redirected to login page
            'access' => [
                'class' => AccessControl::class,
                'only' => ['like', 'dislike'],
                'rules' => [
                    [
                        'allow' => true,
                        'roles' => ['@']
                    ]
                ]
            ],
            // Only POST method is allowed for Like & Dislike button
            'verb' => [
                'class' => VerbFilter::class,
                'actions' => [
                    'like' => ['post'],
                    'dislike' => ['post'],
                ]
            ]
        ];
    }
..
}
frontend > controllers > VideoController.php

4) Use Pjax for the buttons

Pjax is a widget integrating the pjax jQuery plugin. We use it so that when user click on the button, the page won't get reloaded. Instead, it only change the state of the button.

19
20
21
22
class VideoLike extends \yii\db\ActiveRecord
{
    const TYPE_LIKE = 1;
    const TYPE_DISLIKE = 2;
common > models > VideoLike.php

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
    public function actionLike($id)
    {
        $video = $this->findVideo($id);
        $userId = \Yii::$app->user->id;

        $videoLike = new VideoLike();
        $videoLike->video_id = $id;
        $videoLike->user_id = $userId;
        $videoLike->type = VideoLike::TYPE_LIKE;
        $videoLike->created_at = time();
        $videoLike->save();

        return $this->renderAjax('_buttons', [
            'model' => $video
        ]);
    }
frontend > controllers > VideoController.php
19
20
21
22
23
24
25
            <div>
                <?php Pjax::begin() ?>
                <?php echo $this->render('_buttons', [
                    'model' => $model
                ]) ?>
                <?php Pjax::end() ?>
            </div>
frontend > views > video > view.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<?php

/** @var $model \common\models\Video */

use yii\helpers\Url;


?>


<a href="<?php echo Url::to(['/video/like', 'id' => $model->video_id]) ?>" class="btn btn-sm btn-outline-primary"
    data-method="post" data-pjax="1">
    <i class="fa-solid fa-thumbs-up"></i> 9
</a>
<button class="btn btn-sm btn-outline-secondary">
    <i class="fa-regular fa-thumbs-down"></i> 3
</button>
frontend > views > video > _buttons.php (new file)

When you clicked on the like button, you can see there's a new request under the Network tab. There's also a new data with a type of 1 in database.



5) Avoid multiple likes from 1 user

28
29
..
207
208
209
210
211
212
213
214
class Video extends \yii\db\ActiveRecord
{
..
    public function isLikedBy($userId)
    {
        return VideoLike::find()
            ->userIdVideoId($userId, $this->video_id)
            ->liked()
            ->one();
    }
}
common > models > Video.php

3
4
5
..
37
38
39
40
41
42
43
44
45
46
47
48
49
namespace common\models\query;

use common\models\VideoLike;
..
    public function userIdVideoId($userId, $videoId)
    {
        return $this->andWhere([
            'video_id' => $videoId,
            'user_id' => $userId
        ]);
    }

    public function liked()
    {
        return $this->andWhere(['type' => VideoLike::TYPE_LIKE]);
    }
}
common > models > query > VideoLikeQuery.php

70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
    public function actionLike($id)
    {
        $video = $this->findVideo($id);
        $userId = \Yii::$app->user->id;

        $videoLikeDislike = VideoLike::find()
            ->userIdVideoId($userId, $id)
            ->one();

        // If user clicked the buttons for the first time, save the LIKE
        if (!$videoLikeDislike) {
            $this->saveLikeDislike($id, $userId, VideoLike::TYPE_LIKE);
        }
        // If user already clicked the LIKE button, delete the LIKE
        else if ($videoLikeDislike->type == VideoLike::TYPE_LIKE) {
            $videoLikeDislike->delete();
        }
        // If user already clicked the DISLIKE button and then click LIKE, delete the DISLIKE and save new LIKE
        else {
            $videoLikeDislike->delete();
            $this->saveLikeDislike($id, $userId, VideoLike::TYPE_LIKE);
        }

        return $this->renderAjax('_buttons', [
            'model' => $video
        ]);
    }

    public function findVideo($id)
    {
        $video = Video::findOne($id);

        // Display error if video id does not exist.
        if (!$video) {
            throw new NotFoundHttpException("Video does not exist.");
        }

        return $video;
    }

    protected function saveLikeDislike($videoId, $userId, $type)
    {
        $videoLikeDislike = new VideoLike();
        $videoLikeDislike->video_id = $videoId;
        $videoLikeDislike->user_id = $userId;
        $videoLikeDislike->type = $type;
        $videoLikeDislike->created_at = time();
        $videoLikeDislike->save();
    }
frontend > controllers > VideoController.php

11
12
13
14
15
<a href="<?php echo Url::to(['/video/like', 'id' => $model->video_id]) ?>"
    class="btn btn-sm <?php echo $model->isLikedBy(Yii::$app->user->id) ? 'btn-outline-primary' : 'btn-outline-secondary' ?>"
    data-method="post" data-pjax="1">
    <i class="fa-solid fa-thumbs-up"></i> 9
</a>
frontend > views > video > _buttons.php



6) Count and display number of likes


124
125
126
127
128
129
130
131
132
133
134
135
    public function getViews()
    {
        return $this->hasMany(VideoView::class, ['video_id' => 'video_id']);
    }

    /** @return \yii\db\ActiveQuery */

    public function getLikes()
    {
        return $this->hasMany(VideoLike::class, ['video_id' => 'video_id'])
            ->liked();
    }
common > models > Video.php

11
12
13
14
15
<a href="<?php echo Url::to(['/video/like', 'id' => $model->video_id]) ?>"
    class="btn btn-sm <?php echo $model->isLikedBy(Yii::$app->user->id) ? 'btn-outline-primary' : 'btn-outline-secondary' ?>"
    data-method="post" data-pjax="1">
    <i class="fa-solid fa-thumbs-up"></i> <?php echo $model->getLikes()->count() ?>
</a>
frontend > views > video > _buttons.php

7) Count and display number of dislikes


129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
..
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
    /** @return \yii\db\ActiveQuery */

    public function getLikes()
    {
        return $this->hasMany(VideoLike::class, ['video_id' => 'video_id'])
            ->liked();
    }

    /** @return \yii\db\ActiveQuery */

    public function getDislikes()
    {
        return $this->hasMany(VideoLike::class, ['video_id' => 'video_id'])
            ->disliked();
    }
..
    public function isLikedBy($userId)
    {
        return VideoLike::find()
            ->userIdVideoId($userId, $this->video_id)
            ->liked()
            ->one();
    }

    public function isDislikedBy($userId)
    {
        return VideoLike::find()
            ->userIdVideoId($userId, $this->video_id)
            ->disliked()
            ->one();
    }
common > models > Video.php

45
46
47
48
49
50
51
52
53
    public function liked()
    {
        return $this->andWhere(['type' => VideoLike::TYPE_LIKE]);
    }

    public function disliked()
    {
        return $this->andWhere(['type' => VideoLike::TYPE_DISLIKE]);
    }
common > models > query > VideoLikeQuery.php

 70
 71
..
 97
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
    public function actionLike($id)
    {
        ...
    }

    public function actionDislike($id)
    {
        $video = $this->findVideo($id);
        $userId = \Yii::$app->user->id;

        $videoLikeDislike = VideoLike::find()
            ->userIdVideoId($userId, $id)
            ->one();

        // If user clicked the buttons for the first time, save the DISLIKE
        if (!$videoLikeDislike) {
            $this->saveLikeDislike($id, $userId, VideoLike::TYPE_DISLIKE);
        }
        // If user already clicked the DISLIKE button, delete the DISLIKE
        else if ($videoLikeDislike->type == VideoLike::TYPE_DISLIKE) {
            $videoLikeDislike->delete();
        }
        // If user already clicked the LIKE button and then click DISLIKE, delete the LIKE and save new DISLIKE
        else {
            $videoLikeDislike->delete();
            $this->saveLikeDislike($id, $userId, VideoLike::TYPE_DISLIKE);
        }

        return $this->renderAjax('_buttons', [
            'model' => $video
        ]);
    }
frontend > controllers > VideoController.php

11
12
13
14
15
16
17
<a href="<?php echo Url::to(['/video/like', 'id' => $model->video_id]) ?>" class="btn btn-sm <?php echo $model->isLikedBy(Yii::$app->user->id) ? 'btn-outline-primary' : 'btn-outline-secondary' ?>" data-method="post" data-pjax="1">
    <i class="fa-solid fa-thumbs-up"></i> <?php echo $model->getLikes()->count() ?>
</a>

<a href="<?php echo Url::to(['/video/dislike', 'id' => $model->video_id]) ?>" class="btn btn-sm <?php echo $model->isDislikedBy(Yii::$app->user->id) ? 'btn-outline-primary' : 'btn-outline-secondary' ?>" data-method="post" data-pjax="1">
    <i class="fa-solid fa-thumbs-down"></i> <?php echo $model->getDislikes()->count() ?>
</a>
frontend > views > video > _buttons.php




No comments:

Post a Comment