


In this Programming With Yii2 series, I'm guiding readers in use of the Yii2 Framework for PHP. You may also be interested in my Introduction to the Yii Framework, which reviews the benefits of Yii and includes an overview of what's new in Yii 2.x.
Introduction
In today's tutorial, I'm going to show you how to extend Yii to easily mimic a site like Reddit with voting, comments, and sharing.
Recently, I've been working on creating my own personal extension of the great Yii advanced template. The template provides built-in user registration and authentication and multiple sites for front-end and administrative websites.
I built some my latest Twitter API episodes on the early version of this platform, following friends on behalf of users and analyzing our followers. The site I described in those, Twixxr, forms the foundation of my Yii customization work.
So adding core functionality like voting, comments and sharing makes so much sense. As you expand your Yii codebase with these kinds of features, building new sites becomes faster, easier and increasingly powerful.
Getting Started
I'm going to walk you through using three Yii2 plugins:
- Chiliec's Yii2 Voting Extension
- 2Amigos Yii2 Disqus Comments Extension
- Kartik's Yii2 Social Extension
They make it relatively fast and easy to build a powerful social community on Yii2.
I've created a model called Item
which represents an object that you want users to vote on, comment on, and share.
Frankly, after building the item pages with these features in my platform, I felt more impressed than ever with Yii... more impressed than I've been to date even building my startup series. You can do so much with this framework.
Let's dig in.
Installing the Extensions
First, let's add all three extensions to composer.json at once:
1 |
{
|
2 |
"name": "yiisoft/yii2-app-advanced", |
3 |
"description": "Yii 2 Advanced Project Template", |
4 |
"keywords": ["yii2", "framework", "advanced", "project template"], |
5 |
"homepage": "https://www.yiiframework.com/", |
6 |
"type": "project", |
7 |
"license": "BSD-3-Clause", |
8 |
"support": { |
9 |
"issues": "https://github.com/yiisoft/yii2/issues?state=open", |
10 |
"forum": "http://www.yiiframework.com/forum/", |
11 |
"wiki": "http://www.yiiframework.com/wiki/", |
12 |
"irc": "irc://irc.freenode.net/yii", |
13 |
"source": "https://github.com/yiisoft/yii2" |
14 |
},
|
15 |
"minimum-stability": "stable", |
16 |
"require": { |
17 |
"php": ">=5.4.0", |
18 |
"yiisoft/yii2": ">=2.0.10", |
19 |
"yiisoft/yii2-bootstrap": "*", |
20 |
"yiisoft/yii2-swiftmailer": "*", |
21 |
"yiisoft/yii2-authclient": "~2.1.0", |
22 |
"google/apiclient": "1.0.*@beta", |
23 |
"machour/yii2-google-apiclient":"@dev", |
24 |
"machour/yii2-google-gmail": "@dev", |
25 |
"ruskid/yii2-stripe": "dev-master", |
26 |
"2amigos/yii2-disqus-widget":"~1.0", |
27 |
"abraham/twitteroauth":"*", |
28 |
"codeception/codeception":"*", |
29 |
"notamedia/yii2-sentry": "^1.1", |
30 |
"chiliec/yii2-vote": "^4.0", |
31 |
"yiidoc/yii2-redactor": "*", |
32 |
"kartik-v/yii2-social": "@dev" |
Then run composer update
.
Adding Voting
Vladimir Babin is Chiliec, and I very much like the way he and others have collaborated to create this plugin. All the basic features that you want are included, and you can easily customize it, specifically by overriding the view. They have great documentation and keep it well updated too.
Here's a helpful animated gif of the plugin's default features which they host on GitHub. I've posted a static image below (Envato Tuts+ doesn't support gifs in our tutorials).



Of course, I decided to customize the view and eliminate down votes, and it was fairly easy.
Configuration
Next, we add the voting plugin to /active/config/main.php so that it's loaded everywhere in bootstrap and configured for our application:
1 |
return [ |
2 |
'id' => 'app-active', |
3 |
'basePath' => dirname(__DIR__), |
4 |
'bootstrap' => ['chiliec\vote\components\VoteBootstrap', |
5 |
'log','\common\components\SiteHelper'], |
6 |
'modules' => [ |
7 |
... |
8 |
'vote' => [ |
9 |
'class' => 'chiliec\vote\Module', |
10 |
// show messages in popover |
11 |
'popOverEnabled' => true, |
12 |
// global values for all models |
13 |
// 'allowGuests' => true, |
14 |
// 'allowChangeVote' => true, |
15 |
'models' => [ |
16 |
1 => [ |
17 |
'modelName' => \active\models\Item::className(), |
18 |
'allowGuests' => false, |
19 |
], |
20 |
// example declaration of models |
21 |
// \common\models\Post::className(), |
22 |
// 'backend\models\Post', |
23 |
// 2 => 'frontend\models\Story', |
24 |
// 3 => [ |
25 |
// 'modelName' => \backend\models\Mail::className(), |
26 |
// you can rewrite global values for specific model |
27 |
// 'allowGuests' => false, |
28 |
// 'allowChangeVote' => false, |
29 |
// ], |
30 |
], |
31 |
], |
32 |
You can see that I've turned off guest voting so that people are required to sign up to vote on items.
Database Integration
Next, you have to run the database migration to create tables that track the votes.
1 |
$ php yii migrate/up --migrationPath=@vendor/chiliec/yii2-vote/migrations |
It's important to remember to run this migration when installing your product server! It's quite easy to forget.
Displaying the Voting Widget
My item model is part of a collection model called Topic, so you can find the partial view for my voting widget in /views/topic/_item.php:
1 |
<?php
|
2 |
use yii\helpers\Html; |
3 |
use yii\helpers\HtmlPurifier; |
4 |
use dosamigos\disqus\CommentsCount; |
5 |
use kartik\social\TwitterPlugin; |
6 |
use kartik\social\FacebookPlugin; |
7 |
use yii\helpers\StringHelper; |
8 |
|
9 |
?>
|
10 |
<div class="item_row row"> |
11 |
<div class="col-xs-1 col-md-1 col-lg-1"> |
12 |
<?= \chiliec\vote\widgets\Vote::widget([ |
13 |
'model' => $model, |
14 |
// optional fields
|
15 |
'showAggregateRating' => false, |
16 |
]);?> |
17 |
</div>
|
Topic index calls displays a grid which displays _item.php
as a row. I didn't want to display a rating, just the positive vote totals, so I set that to false.
To override the view, I created /views/vote/vote.php:
1 |
<div class="vote-row text-center" id="vote-<?=$modelId?>-<?=$targetId?>" data-placement="top" data-container="body" data-toggle="popover"> |
2 |
<span class="glyphicon glyphicon-chevron-up" onclick="vote(<?=$modelId?>, <?=$targetId?>, 'like'); return false;" style="cursor: pointer;"></span><br /><span id="vote-up-<?=$modelId?>-<?=$targetId?>"><?=$likes?></span> |
3 |
<div id="vote-response-<?=$modelId?>-<?=$targetId?>"> |
4 |
<?php if ($showAggregateRating) { ?> |
5 |
<?=Yii::t('vote', 'Aggregate rating')?>: <?=$rating?> |
6 |
<?php } ?> |
7 |
</div>
|
8 |
</div>
|
9 |
<div itemprop="aggregateRating" itemscope itemtype="http://schema.org/AggregateRating"> |
10 |
<meta itemprop="interactionCount" content="UserLikes:<?=$likes?>"/> |
11 |
<meta itemprop="interactionCount" content="UserDislikes:<?=$dislikes?>"/> |
12 |
<meta itemprop="ratingValue" content="<?=$rating?>"/> |
13 |
<meta itemprop="ratingCount" content="<?=$likes+$dislikes?>"/> |
14 |
<meta itemprop="bestRating" content="10"/> |
15 |
<meta itemprop="worstRating" content="0"/> |
16 |
</div>
|
Not a lot of plugins make overriding so easy.
I removed the vote down icon and changed the vote up icon to a chevron. Here's what it looks like now:



I know this seems like a lot of layers, but it actually didn't take too long to make it work.
Adding Disqus Comments
Next, I created a Disqus site for the upcoming site, ActiveTogether.org, which will be available for you to see these features in action by the time you read this. Thus, the Disqus site shortname is 'active-together'.
I began using 2Amigos' widget before I integrated Kartik's social extension (discussed below), which also offers Disqus comments.
Creating a Unique Identifier for Each Comment Board
Whenever a new Item is created, the Item::beforeSave()
action creates a unique identifier for Disqus to link comments too. You can also rely on the URL of a page, but this is more predictable generally.
In other words, Disqus collates all comments for each item separately, and that helps makes up each item's comment board.
1 |
public function beforeSave($insert) |
2 |
{
|
3 |
if (parent::beforeSave($insert)) { |
4 |
if ($insert) { |
5 |
$this->identifier = Yii::$app->security->generateRandomString(8); |
6 |
$this->site_id = Yii::$app->params['site']['id']; |
7 |
}
|
8 |
}
|
9 |
return true; |
10 |
}
|
Displaying the Comments
Then, the comments board is easily displayed at the bottom of the Item view in /active/views/Item.php:
1 |
<?php
|
2 |
|
3 |
use yii\helpers\Html; |
4 |
use yii\helpers\HtmlPurifier; |
5 |
use yii\helpers\Url; |
6 |
use dosamigos\disqus\Comments; |
7 |
...
|
8 |
<?= Comments::widget([ |
9 |
'shortname' => 'active-together', |
10 |
'identifier'=>$model->identifier, |
11 |
]); ?> |
Notice how the widget needs the shortname
and the identifier
to provide Disqus for the comments.
Here's an example of what the comments board looks like:



The Index View With Comment Counts
2Amigos also leverages the Disqus JavaScript libraries for displaying comment counts. But there are a few pieces to put together to make this happen.
First, I created a jQuery script to request an item's comment counts. When there are lots of items on a page, you need to request it with reset: true;
:
1 |
$(document).ready(function(){ |
2 |
DISQUSWIDGETS.getCount({reset: true}); |
3 |
});
|
Then I created a TopicAsset.php file to load the .js file:
1 |
<?php
|
2 |
namespace active\assets; |
3 |
use yii\web\AssetBundle; |
4 |
|
5 |
class TopicAsset extends AssetBundle |
6 |
{
|
7 |
public $basePath = '@webroot'; |
8 |
public $baseUrl = '@active'; |
9 |
public $css = [ |
10 |
|
11 |
];
|
12 |
public $js = [ |
13 |
'js/topic.js', |
14 |
];
|
15 |
public $depends = [ |
16 |
'yii\web\YiiAsset', |
17 |
'yii\bootstrap\BootstrapAsset', |
18 |
];
|
Then, the /active/views/Topic.php file registers the TopicAsset bundle
:
1 |
<?php
|
2 |
|
3 |
use yii\helpers\Html; |
4 |
use yii\grid\GridView; |
5 |
use yii\widgets\Breadcrumbs; |
6 |
use common\widgets\Alert; |
7 |
use active\assets\TopicAsset; |
8 |
TopicAsset::register($this); |
Next, each _item.php partial includes a comment count:
1 |
<p><?= $this->render('_social', ['model' => $model, |
2 |
'includeCommentCount'=>true]);?></p> |
And the _social
partial displays it like this using each Item->identifier:
1 |
<li class="share_adjust_vert"><?= Html::a(Yii::t('active','Comments') |
2 |
,['/item/'.$model->slug.'#disqus_thread'], |
3 |
['data-disqus-identifier'=>$model->identifier]) ?> |
4 |
<?= CommentsCount::widget([ |
5 |
'shortname' => 'active-together', |
6 |
'identifier' => $model->identifier, |
7 |
]);
|
8 |
?>
|
In order for Disqus to find where to update elements with comment counts, each link must end with #disqus_thread
.
Here's what that page looks like. Each item has a distinct comment count loaded by referencing its identifier:



Let's move on to those social sharing buttons you've been seeing.
Adding Social Sharing
Kartik's done a great job with his social widget building a basic configuration for connection to a number of social companies like Twitter, Disqus, and Facebook. For now, I'm only using the Facebook share button. Twitter's share button doesn't have very good aesthetics, so I replaced it with an HTML web intents link.
Here's my code for the pair of buttons beside the comments count in /active/views/topic/_social.php:
1 |
</li>
|
2 |
<li class="share_adjust_vert"><a class="twitter-share" |
3 |
href="https://twitter.com/intent/tweet? |
4 |
text=<?= urlencode($model->title); ?> |
5 |
&url=<?= urlencode(Url::canonical());?> |
6 |
&via=<?= Yii::$app->params['site']['twitter_account']?> |
7 |
"><img src="<?= Url::to(Url::home(true).'/images/social/twitter_icon.png'); |
8 |
?>"> Tweet</a></li> |
9 |
<li><?= FacebookPlugin::widget |
10 |
(['type'=>FacebookPlugin::SHARE, |
11 |
'settings' => ['dataSize'=>'small', |
12 |
'class'=>"fb_iframe_widget"]]); ?></li> |
13 |
</ul>
|
Seems simple, except that vertically aligning Facebook's widget requires some CSS adjustments. In /active/views/topic/_grid.php, I placed this adjustment:
1 |
<style media="screen" type="text/css"> |
2 |
.fb_iframe_widget span |
3 |
{
|
4 |
vertical-align: baseline !important; |
5 |
}
|
6 |
</style>
|
It has to come after the other CSS files load.
And, in the site.css file, I placed this to get the precise look I wanted:
1 |
.share_adjust_vert { |
2 |
margin-top:-1px; |
3 |
font-size:90%; |
4 |
vertical-align: top; |
5 |
} |
Wrapping Up
Honestly, I'm so excited at how easy it was to use Yii and essentially create a mini social clone. These are great plugins for a great framework, and generally Yii's developers and its community of plugin developers are responsive on GitHub with questions and issues.
I hope you are eager to check out ActiveTogether and try out this framework for yourself.
If you have any questions or suggestions, please post them in the comments. If you'd like to keep up on my future Envato Tuts+ tutorials and other series, please visit my instructor page or follow @lookahead_io. Definitely check out my startup series and Meeting Planner.