In this tutorial, we will start building the core part of CapForum, which is the forum.
Download link of entire source code is provided in part 4 of this series.
We create four models representing four database tables. And four tables are tied together with following relationships:
Let us create model files:
"app/Model/User.php":
<?php
App::uses('AppModel', 'Model');
class User extends AppModel {
public $validate = array(
'username' => array(
'notEmpty' => array(
'rule' => array('notEmpty')
),
),
'password' => array(
'notEmpty' => array(
'rule' => array('notEmpty')
),
),
'email' => array(
'email' => array(
'rule' => array('email')
),
),
);
//The Associations below have been created with all possible keys, those that are not needed can be removed
/**
* hasMany associations
*
* @var array
*/
public $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'user_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
'Topic' => array(
'className' => 'Topic',
'foreignKey' => 'user_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
"app/Model/Forum.php":
<?php
App::uses('AppModel', 'Model');
class Forum extends AppModel {
public $validate = array(
'name' => array(
'notEmpty' => array(
'rule' => array('notEmpty')
),
),
);
public $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'forum_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
'Topic' => array(
'className' => 'Topic',
'foreignKey' => 'forum_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
"app/Model/Topic.php":
<?php
App::uses('AppModel', 'Model');
class Topic extends AppModel {
public $validate = array(
'name' => array(
'notEmpty' => array(
'rule' => array('notEmpty')
),
),
'content' => array(
'notEmpty' => array(
'rule' => array('notEmpty')
),
),
'forum_id' => array(
'numeric' => array(
'rule' => array('numeric')
),
),
);
/**
* belongsTo associations
*
* @var array
*/
public $belongsTo = array(
'Forum' => array(
'className' => 'Forum',
'foreignKey' => 'forum_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
/**
* hasMany associations
*
* @var array
*/
public $hasMany = array(
'Post' => array(
'className' => 'Post',
'foreignKey' => 'topic_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
}
"app/Model/Post.php":
<?php
App::uses('AppModel', 'Model');
class Post extends AppModel {
public $validate = array(
'topic_id' => array(
'numeric' => array(
'rule' => array('numeric')
),
),
'forum_id' => array(
'numeric' => array(
'rule' => array('numeric')
),
),
'content' => array(
'notEmpty' => array(
'rule' => array('notEmpty')
),
),
'user_id' => array(
'numeric' => array(
'rule' => array('numeric')
),
),
);
/**
* belongsTo associations
*
* @var array
*/
public $belongsTo = array(
'Topic' => array(
'className' => 'Topic',
'foreignKey' => 'topic_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Forum' => array(
'className' => 'Forum',
'foreignKey' => 'forum_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
We will also use Containable behavior from CakePHP. Containable behavior is good for controlling model find operations. To add the behavior to all models, we can do from AppModel class.
"app/Model/AppModel.php":
<?php
App::uses('Model', 'Model');
class AppModel extends Model {
public $actsAs = array('Containable');
}
We want CapForum to show forum index page whenever user access the URL directly. This can be handled by Router.
Open file "app/Config/routes.php" and add following line to the begining of the file:
Router::connect('/', array('controller' => 'forums', 'action' => 'index'));
This tells the router to serve the forum's index page as the default page.
Before we move to create the actual forum's index page. Let us create a reusable paginator element first. The first reason we want to create an element for paginator is that, we will need this paginator in several pages, so creating an element makes the view look cleaner. Second reason is that, by default, Bootstrap's paginator does not work with the CakePHP generated paginator out of box, we need to add some custom CSS to make them work together.
Copy following to "app/View/Elements/paginator.ctp". This element is very useful, it will not only work for CapForum, but also work with any Bootstrap based paginator. Feel free to use it for your other projects.
<style>
ul.pagination .active,
ul.pagination .disabled {
float: left;
padding: 3px 11px 4px 11px;
text-decoration: none;
border: 1px solid;
}
ul.pagination .active {
background-color: #428bca;
border-color: #428bca;
}
ul.pagination .disabled {
color: #999;
cursor: not-allowed;
background-color: #fff;
border-color: #ddd;
}
ul.pagination > li:first-child {
border-left-width: 1px;
-webkit-border-bottom-left-radius: 4px;
border-bottom-left-radius: 4px;
-webkit-border-top-left-radius: 4px;
border-top-left-radius: 4px;
-moz-border-radius-bottomleft: 4px;
-moz-border-radius-topleft: 4px;
}
ul.pagination > li:last-child {
-webkit-border-top-right-radius: 4px;
border-top-right-radius: 4px;
-webkit-border-bottom-right-radius: 4px;
border-bottom-right-radius: 4px;
-moz-border-radius-topright: 4px;
-moz-border-radius-bottomright: 4px;
}
</style>
<ul class="pagination pagination-sm">
<?php
echo $this->Paginator->prev('← ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'disabled', 'escape' => false));
echo $this->Paginator->numbers(array('separator' => '', 'tag' => 'li', 'currentClass' => 'active'));
echo $this->Paginator->next(__('next') . ' →', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'disabled', 'escape' => false));
?>
</ul>
We have told the router to serve forum index page as the default page, however we have not actually created this page yet.
First let us create "app/Controller/ForumsController.php":
<?php
App::uses('AppController', 'Controller');
class ForumsController extends AppController {
public $components = array('Paginator');
public function beforeFilter() {
$this->Auth->allow();
}
public function index() {
$this->Paginator->settings['contain'] = array('Topic', 'Post'=>array('User','Topic'));
$this->set('forums', $this->Paginator->paginate());
}
}
This controller is very simple. It allows all public access, thus we use $this->Auth->allow();. We use Paginator component to find forum data for the view.
Next we need to create a view "app/View/Forums/index.ctp" for "public function index()":
<div class="row">
<div class="col-lg-12 ">
<table class="table table-bordered">
<thead>
<tr>
<th colspan=2>Forum</th>
<th>Topics</th>
<th>Posts</th>
<th>Activity</th>
</tr>
</thead>
<tbody>
<?php foreach ($forums as $forum): ?>
<tr>
<td> </td>
<td>
<?php
echo $this->Html->link('<h4>'.$forum['Forum']['name'].'</h4>',
array('controller'=>'topics','action'=>'index',$forum['Forum']['id']),
array('escape'=>false));
?>
</td>
<td><?php echo count($forum['Topic']);?></td>
<td><?php echo count($forum['Post']);?></td>
<td>
<?php
if(count($forum['Post'])>0) {
$post = $forum['Post'][0];
echo $this->Html->link($post['Topic']['name'],array('controller'=>'topics',
'action'=>'view',
$post['Topic']['id']));
echo ' ';
echo $this->Time->timeAgoInWords($post['created']);
echo ' <small>by</small> ';
echo ' ';
echo $this->Html->link($post['User']['username'],array('controller'=>'users',
'action'=>'profile',
$post['User']['id']));
}
?>
</td>
</tr>
<?php endforeach;?>
</tbody>
</table>
<div class="pull-right">
<?php
echo $this->element('paginator');
?>
</div>
</div>
</div>
Couple of points to take note in the view file.
If you have followed everything correctly. You should be able to view the forum index page as below:
Next tutorial, we are going to write the last part of this series. Which covers pages for viewing topics, adding topic, posting replies as well as some wrap-up tasks.
Hopefully this simple tutorial helped you with your development. If you like our post, please follow us on Twitter and help spread the word. We need your support to continue. If you have questions or find our mistakes in above tutorial, do leave a comment below to let us know.