Hands-on Project: Twitter Clone - Tweet, User Feed and Profile
September 20, 2018
Welcome to the second part of the three part PHP Twitter Clone project. In this part, you will add the following functionalities to the Twitter Clone: (a) user can post tweets, (b) the tweets will be displayed in the user feed, and (c) profile page for the users. Let’s begin!
Step 1: Tweets table
We have to create a table to store all the tweet information. Go ahead and create the tweets
table in phpMyAdmin inside the twitter
database.
What columns should the table have? What attributes should each column have? What should be the max-length for the tweet text?
Solution to this section can be found in section Solution: Tweets table below.
Step 2: Submitting tweets
In home.php
, you will see a text area in which a user can type in a tweet.
Once the user presses the Tweet
button, we want to do the following:
- Check if the tweet is empty or if the tweet length exceeds 140 characters and display appropriate error messages by setting the
$error
variable. - If the tweet has no issues, insert the tweet information into the
tweets
table you just created.
Hint:
For the time at which the tweet was posted, use the date("Y-m-d H:i:s")
function to get the date and time in the form yyyy-mm-dd hr-min-sec
(this is the datetime format accepted in SQL).
Your browser might re-submit a form when you refresh. This might cause duplicate tweets if you refresh your browser right after tweeting.
Checkpoint: Refresh the page and write a tweet (for example: “Hello there!”) and click the Tweet
button.
You won’t see anything happening in the home page. But if you go to phpMyAdmin and check the tweets
table, you will find your tweet there:
Also check if you get the appropriate error when you submit an empty tweet or a tweet which exceeds the maximum limit of 140 characters.
Solution to this section can be found in section Solution: Submitting tweets below.
Amazing! You created the tweets
table and wrote PHP code to insert the tweet information into this table.
Step 3: User Feed
Okay, so the tweets are stored in the tweets table. What next?
We want to:
- Display the tweets on the home page. Thus,
home.php
will now contain the user feed. - Display the number of tweets posted by the user in the user feed page.
Open the project-resources
folder and replace the HTML in home.php
(that you copied from home_1.html
) with the HTML inside home_2.html
.
Let’s create two methods in the Tweet
class which will perform the above tasks. Here’s the code template to get you started:
<!-- tweet.php -->
<?php
class Tweet extends Base {
function __construct($pdo) {
$this->pdo = $pdo;
}
public function tweets($user_id) {
// Your code here ...
// Fetch tweets authored by user_id. Sorted by newest first.
// (You will need to do a JOIN query on users and tweets tables.
// Store the result in $tweets
foreach ($tweets as $tweet) {
echo '<div class="all-tweet">
<div class="t-show-wrap">
<div class="t-show-inner">
<div class="t-show-popup">
<div class="t-show-head">
<div class="t-show-img">
<img src="'. $tweet->profileImage .'"/>
</div>
<div class="t-s-head-content">
<div class="t-h-c-name">
<span><a href="profile.php?username='. $tweet->username .'">'. $tweet->fullname .'</a></span>
<span>@'. $tweet->username .'</span>
<span>'. $tweet->postedOn .'</span>
</div>
<div class="t-h-c-dis">
'. $tweet->status .'
</div>
</div>
</div>
</div>
<div class="t-show-footer">
<div class="t-s-f-right">
<ul>
<li><button><a href="#"><i class="fa fa-retweet" aria-hidden="true"></i></a></button></li>
<li><button><a href="#"><i class="fa fa-heart-o" aria-hidden="true"></i></a></button></li>
<li>
<a href="#" class="more"><i class="fa fa-ellipsis-h" aria-hidden="true"></i></a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>';
}
}
public function countTweets($user_id) {
// Your code here ...
// echo count of number of tweets by user user_id.
}
}
?>
Here, the tweets()
method displays the tweets enclosed in HTML tags using a foreach
-loop and echo
statement. The HTML assumes that the columns of the tweet table have specific names — such as status
, postedOn
, etc. Edit the PHP inside the above HTML if you chose different column names.
Note: Later on (in part 3 of the project), you will need to update the query inside the
tweets()
method to also display tweets from users that the current user is following.
Checkpoint: That’s all! Refresh the home page and you will see something like this:
User Feed in home page
You will see the tweet you just posted and on the left hand side, the tweets count has been updated to one! Post a couple tweets to ensure that everything is working as expected.
Solution to this section can be found in section Solution: User Feed below.
Step 4: User Profile
When you click on the profile image icon on the top right of the page, you see two options: one is the username of the user (let’s call it profile) and the other is logout. On clicking the first option, we want the user to be redirected to the profile page.
Create a profile.php
page inside the main directory (i.e. the twitter
folder) with open and close PHP tags.
<!-- profile.php -->
<?php ?>
Now open the project-resources
folder and copy the HTML from profile_1.html
and paste it below the PHP tags.
The way the profile page works is, when the user clicks on the profile link, we send the username of the user through a GET
request. The URL is of the form http://localhost/twitter/profile.php?username=jamesmat
.
In profile.php
, you need to get the username from the GET
request. After you get the username, you need to:
- Get the
user_id
from the username. For this, you have to create a methoduserIdByUsername()
in theUser
class which given a username, returns theuser_id
. Assign this to a new variable calledprofileId
. - If the username doesn’t exist in the users table or if someone is trying to access
profile.php
without logging in (you have already implemented this inhome.php
), redirect toindex.php
. - Get the profile data like
fullname
,username
andprofileImage
using theprofileId
that you just got in the previous step. (Tip: Use theuserData()
method in theUser
class). - Get all the tweets of the current user. Create another method in the
Tweet
class which fetches all tweets for a givenprofileId
. Call itgetUserTweets()
. - Go over the
php
tags in the HTML and make sure you have instantiated all the variables that are needed for the page —$user
,$profileId
, and$profileData
.
Create the methods as explained above and then call them in profile.php
.
Take a look at the HTML code (it includes some PHP code). You will see which variables and properties are expected to be there.
Sometimes, there might be errors in your PHP code but you might not be able to see it because the error message is hidden somewhere in the page HTML. The easiest way to spot any error message is to right click anywhere on Chrome when you have the site open, and go to
View Page Source
. That gives you access to the raw HTML of the page, which will include any error messages or notice PHP may have encountered.
Solution to this section can be found in section Solution: User Profile below.
Checkpoint: Refresh the home page and click on the profile link on the top right of the screen. This is what you should see:
User profile
Try to change the username in the URL and see if the corresponding user’s profile is displayed.
You just created the user profile page! Awesome!
Summary
In this part of the project, you created the user feed and the user profile page.
Users can now post tweets and look at other users’ tweets by visiting their profile page through the URL (we will add the search feature in the next part to directly search for a user instead of having to type out the URL).
If you lost track, got stuck, or just need to double check, the solution for each of the above steps is included below. You can also find all the code we have written so far here (that is, what your current code should look like).
In the next (and last) part of the project, you will work add the follow / unfollow feature and the ability to search for a user.
Solution: Tweets table
The tweets
table should have 4 columns:
tweets
table
Here, status
refers to the tweet text which has a limit of 140 characters (just like in Twitter!). tweetBy
stores the user_id
of the user who tweeted the message. postedOn
stores the date and time when the tweet was created which has a type of DATETIME
.
Here is what the table should look while creating:
Make sure A_I
is checked for tweetId
and it is the PRIMARY
key in the table.
Solution: Submitting tweets
home.php
after making the changes to accept tweet:
<!-- home.php -->
<?php
include 'core/init.php';
$user_id = $_SESSION['user_id'];
$user = $getFromU->userData($user_id);
if ($getFromU->loggedIn() === false) {
header('Location: index.php');
}
if (isset($_POST['tweet'])) {
$status = $getFromU->checkInput($_POST['status']);
if (!empty($status) && strlen($status) < 140) {
$getFromU->create('tweets', array('status' => $status, 'tweetBy' => $user_id, 'postedOn' => date("Y-m-d H:i:s")));
} elseif (!empty($status) && strlen($status) > 140) {
$error = "The post is too long! Limit: 140 characters";
}
else {
$error = "Please type something to post!";
}
}
?>
<!-- html goes here -->
You should be familiar with most of this code. The only new thing here is the postedOn
field is set to date("Y-m-d H:i:s")
. This simply says we want the date and time in the form yyyy-mm-dd hr-min-sec
as this is the datetime format accepted in SQL.
Solution: User feed
<!-- tweet.php -->
<?php
class Tweet extends Base {
function __construct($pdo) {
$this->pdo = $pdo;
}
public function tweets($user_id) {
$stmt = $this->pdo->prepare("SELECT * FROM tweets, users WHERE (tweetBy = user_id AND user_id = :user_id) ORDER BY tweetId DESC");
$stmt->bindParam(":user_id", $user_id, PDO::PARAM_INT);
$stmt->execute();
$tweets = $stmt->fetchAll(PDO::FETCH_OBJ);
foreach ($tweets as $tweet) {
echo '<div class="all-tweet">
<div class="t-show-wrap">
<div class="t-show-inner">
<div class="t-show-popup">
<div class="t-show-head">
<div class="t-show-img">
<img src="'. $tweet->profileImage .'"/>
</div>
<div class="t-s-head-content">
<div class="t-h-c-name">
<span><a href="profile.php?username='. $tweet->username .'">'. $tweet->fullname .'</a></span>
<span>@'. $tweet->username .'</span>
<span>'. $tweet->postedOn .'</span>
</div>
<div class="t-h-c-dis">
'. $tweet->status .'
</div>
</div>
</div>
</div>
<div class="t-show-footer">
<div class="t-s-f-right">
<ul>
<li><button><a href="#"><i class="fa fa-retweet" aria-hidden="true"></i></a></button></li>
<li><button><a href="#"><i class="fa fa-heart-o" aria-hidden="true"></i></a></button></li>
<li>
<a href="#" class="more"><i class="fa fa-ellipsis-h" aria-hidden="true"></i></a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>';
}
}
public function countTweets($user_id) {
$stmt = $this->pdo->prepare("SELECT COUNT(tweetId) AS totalTweets FROM tweets where tweetBy = :user_id");
$stmt->bindParam(":user_id", $user_id, PDO::PARAM_INT);
$stmt->execute();
$count = $stmt->fetch(PDO::FETCH_OBJ);
echo $count->totalTweets;
}
}
?>
Solution: User Profile
We first create the userIdByUsername()
method inside the User
class.
public function userIdByUsername($username) {
$stmt = $this->pdo->prepare("SELECT user_id FROM users WHERE username = :username");
$stmt->bindParam(":username", $username, PDO::PARAM_STR);
$stmt->execute();
$user = $stmt->fetch(PDO::FETCH_OBJ);
return $user->user_id;
}
Then create the getUserTweets()
method inside the Tweet
class.
public function getUserTweets($user_id) {
$stmt = $this->pdo->prepare("SELECT * FROM tweets, users where tweetBy = user_id AND user_id = :user_id");
$stmt->bindParam(":user_id", $user_id, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_OBJ);
}
And finally, we will call these functions in profile.php
.
<!-- profile.php -->
<?php
if (isset($_GET['username']) === true && empty($_GET['username']) === false) {
include 'core/init.php';
$username = $getFromU->checkInput($_GET['username']);
$profileId = $getFromU->userIdByUsername($username);
$profileData = $getFromU->userData($profileId);
$tweets = $getFromT->getUserTweets($profileId);
$user_id = $_SESSION['user_id'];
$user = $getFromU->userData($user_id);
if(!$profileData || ($getFromU->loggedIn() === false)) {
header('Location: index.php');
}
} else {
header('Location: index.php');
}
?>
<!-- html goes here -->
Take a look at the HTML code (includes some PHP code). You will get a better idea of how we display all the values that we got in the above code.