Social Network Style Posting with PHP, MongoDB and jQuery – part 2

Tweet
This entry is part 2 of 2 in the series Social Network Style Posting with PHP, MongoDB and jQuery

Social Network Style Posting with PHP, MongoDB and jQuery

In the previous part of the series, we explained the database architecture, post stream design and application flow required for developing our post mechanism wherein the user will be able to post a status, like/unlike other people's statuses and comment on them. This part of the series will drive you through the coding required to implement these functionalities. We will use the application flow and database structure as discussed in the last article. Don't forget to download the code from the github repo if you'd like to follow along.

Insert Post:

In our index.php file, we had added a Create New Post button. Clicking it will call a JavaScript function new_post with the user id from the session as its parameter.

<input type="button" value="Create New Post" id="btn_new_post" 
onClick="new_post('<?php echo $_SESSION['user_id']; ?>')" class="button_style"/>

Referring to the code in script.js, let us understand the function new_post step-by-step:

We first get the post text in a variable new_post_text and check if this text is empty using JavaScript's trim function. If the text is empty, we show an alert message to enter the post text.

function new_post(user_session_id) 
{    
    var new_post_text = $('#post_textarea').val();
    if(!$.trim(new_post_text))
    {
        alert("Please enter some text in the post");
           return;    
    }

We then send an AJAX POST request using jQuery’s $.POST function:

$.post('php_scripts/insert_new_post.php',{
     user_id: user_session_id,
post_text: new_post_text
},function(output){
         // code to be executed after success of AJAX call
    });

The format of jQuery’s $.POST function is:

jQuery.post(url,{parameter:value}, success(output){ //function body });

The first parameter is the url of the file to be called. The second parameter is list of parameters to be sent to the url. The third parameter is a callback function that is executed if the AJAX request succeeds. The variable output contains the output received from the called file.

So, in our case we are calling insert_new_post.php, while passing user id and post text as parameters. We will get back to this function later to write the code inside the callback function.

Next, open the file insert_new_post.php in the php_scripts folder. This is the file wherein we will write the code that interacts with the database. First of all, we create a new post id using new MongoId(). We then get the user id and post text passed from the JS function using the $_POST superglobal. As you might know, a MongoID is a combination of time and other parameters. We fetch the creation time of the post using post_id and the getTimestamp function. We will be using this timestamp as the creation time of our post.

$post_id = new MongoId();
$user_id = new MongoId($_POST['user_id']);
$user_session_id = new MongoId($_SESSION['user_id']);
$post_text = $_POST['post_text'];
$timestamp=date('D, d-M-Y', $post_id->getTimestamp());

Then, we check if the user id received through the AJAX request is same as the current session's user id. This is just a small check to confirm that the file is called from the correct source – we don't want a user performing the actions for someone else. We then create a document containing all the post elements and execute an insert function with the same.

if($user_id == $user_session_id)
{
    $collection = $database->selectCollection('posts_collection');
    $new_post_mongo = array ( '_id'=> $post_id,
                              'post_author_id' => $_SESSION['user_id'],
                              'post_text' => $post_text,
                              'total_likes' => 0,
                              'likes_user_ids' => array (),
                              'comments' => array (),
                              'total_comments' => 0,
                              'timestamp'=>$timestamp
                            );
    $collection->insert($new_post_mongo);                          
}

Once the new document is inserted into the database, we have to show the newly inserted post on our index page. For this we will create and send HTML content from here, and this output will be received as output by the jQuery function new_post which called this page. The code to generate this HTML is similar to what we did to display each post on the home stream in the previous article. So, I am skipping this part of explaining the same code again here. You can refer to the code after query insertion in insert_new_post.php.

Going back to the jQuery function new_post, let us modify the post success logic inside the callback. Here, we prepend the output received to the existing post stream using the prepend method and then display it using jQuery hide and slideDown animation. Lastly, we clear all the existing content of the post textarea.

$.post(insert_new_post_filename,{
    user_id: user_session_id,
    post_text: new_post_text},function(output){
               $('#post_stream').prepend(output);
    var new_post_id=$("#post_stream div:first-child").attr("id");
    $("#"+new_post_id).hide().slideDown();
    $('#post_textarea').val(null);
});

That’s all. We are done with the whole process of inserting a new post and displaying it back on our index page without refreshing the whole page. Let us now quickly see a similar process for liking/unliking the posts.

Like/Unlike Posts:

Locate the span for displaying the Like/Unlike label in index.php. Add an onclick function call post_like_unlike with the current session user id as its parameter.

<span class="post_feedback_like_unlike" id="<?php echo $post_like_unlike_id;?>"  
onclick="post_like_unlike(this,'<?php echo $_SESSION['user_id']; ?>')">
    <?php echo $like_or_unlike; ?>
</span>

In this JS function, we will first check whether the user has clicked Like or Unlike. For this, we grab the HTML text (Like/Unlike) of the span element declared above using the id property of post_id_like_unlike received as the parameter. Here, type will contain the text Like or Unlike.

function post_like_unlike(post_id_like_unlike,user_session_id)
{
    var type = ($('#'+(post_id_like_unlike.id)).html());
    ..

We also get the post id as the first part after splitting the span id. Remember that we had declared span id in the previous article like this:

$post_like_unlike_id=$post_id.'_like_unlike';

The reason for declaring elements like this must be clear to you now. This allows us to use the post id in the JS as we are doing now:

var post_id_of_like_unlike= ((post_id_like_unlike.id).split("_")) [0];
var post_id_like_count = post_id_of_like_unlike+'_like_count';

If the user has clicked on Like we send an AJAX call to post_like.php. Else, we call post_unlike.php. In the callback function, we change the text of Like/Unlike option to Unlike if the user has clicked on Like and vice versa. Also, we increase/decrease the likes count using post_id_like_count.

if (type == 'Like')
{
   $.post('php_scripts/post_like.php',{
        post_id:post_id_of_like_unlike,
        user_id:user_session_id},function(output){
            $('#'+(post_id_like_unlike.id)).html('Unlike');
            $('#'+(post_id_like_count)).html(parseInt($('#'+(post_id_like_count)).html())+1);
       });
}
else 
{
    $.post('php_scripts/post_unlike.php',{
        post_id:post_id_of_like_unlike,
        user_id:user_session_id},function(output){
            $('#'+(post_id_like_unlike.id)).html('Like');
            $('#'+(post_id_like_count)).html(parseInt($('#'+(post_id_like_count)).html())-1);
        });
};

In post_like.php, we update the post document by incrementing total_likes by 1 and pushing the current user id in the array likes_user_ids.

$collection->update(array('_id' =>$post_id),
                    array('$push' => array('likes_user_ids'=>$user_id),
                          '$inc' => array('total_likes' => 1 )) 
                    );

Similarly, in post_unlike.php, we update the post document by decrementing total_likes by 1 and pulling the user id from the array likes_user_ids.

$collection->update(array('_id' => $post_id),
                    array('$pull' => array('likes_user_ids'=>$user_id),
                          '$inc' => array('total_likes' => -1 )) 
                    );

Inserting Comments:

After understanding how the post insertion and like/unlike functionalities work, you will be able to easily understand how the commenting functionality works. After passing the comment text and post id from index.php to JS and then to new_comment.php, we will update the post document to insert the new comment and increment the count.

$collection->update(array('_id' => $post_id),
                    array('$push' => array('comments'=> array (
                                                'comment_id'=>$comment_id,
                                                'comment_user_id' =>$user_id,
                                                'comment_text' => $comment_text
                                               )),      
                           '$inc' => array('total_comments' => 1 ))
                    );

We will then append this new comment to the existing list of comments on the post.

Finally, we have implemented everything we've set out to implement. Fig 1 shows what our final working application looks like. (Note that in the figure, you can see three users. For the purpose of this demonstration, I have changed these users from the session_variables.php file.)

Conclusion:

In the two articles of this series, we learned how to design the database schema, understood the HTML structure, and then implemented the whole post mechanism. However, note that the article only shows one way of achieving these features, which may not be the same approach such social networks use. Also, there are lots of other features and issues (security, design, etc.) to be considered in such applications that we may not have discussed here.

Furthermore, the code does not follow best practices – this is something for the reader to experiment with. Implement object oriented code, use a JavaScript framework, use a PHP framework for the entire back end – the sky is the limit. This post mechanism is merely a hint of how you can implement a single feature in your own social network.

To conclude, this article is just a foundation of many things you can achieve using similar concepts. You can go ahead and implement a lot of features which I could not explain here like displaying names of users who liked the post, showing collapsible comments, deleting comments, deleting posts, liking/unliking comments, popular posts, building a more interactive post stream showing posts from user’s friends and other advanced features. If you have any suggestions or critiques, please leave them in the comments below.

Social Network Style Posting with PHP, MongoDB and jQuery

<< Social Network Style Posting with PHP, MongoDB and jQuery – part 1

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • Michael

    Just curious. Why do you choose MongoDB to this job? It a db for documents not for relationships between values.

  • John a

    Au contraire, MongoDB is perfectly good at relations, it just does not use SQL, nor enforce integrity.

  • Anonymous

    So, two things:
    1) I’m not sure why you’re taking in a user ID on the posts back to the PHP scripts when you have that ID in the session. Why not just use that? Remember, people can call Javascript directly from the console, so they could put in whatever value they want for the user ID

    2) There’s no input filtering on the inserts into the DB nor output escaping on the push of the post and comments back into the page. Have you tested this with HTML content in the comments? Does it leave you open to cross-site scripting attacks? (XSS)

  • Ashish

    @Michael4: We have used MongoDB in our company replacing it by MySQL. One of the prime features I liked about MongoDB was the schema-less approach. In a growing application like ours where new things keep on coming everyday, schema-less approach is very beneficial. And as far as designing relationships is concerned, it is better to fetch data from 2 documents than to maintain it in 5-6 tables. Also, comparing the results in both the cases, we got better performance in case of MongoDB.

  • Ashish

    @Chris: Both your points are valid. But as I mentioned in article, the article does not discuss all the security aspects that come into picture with a real application. You would surely have to use HTML/JS parsing to avoid XSS attacks. We do it in our application through our own library we have developed.