Ribbit Instagram Clone em PHP
Ribbit Instagram Clone em PHP
I like to start with the Model when building this type of application--everything tends
Object 2
The Database
We require four tables for this application. They are:
I'll show you how to create these tables from the terminal. If you use an admin
program (such as phpMyAdmin), then you can either click the SQL button to directly
enter the commands or add the tables through the GUI.
Users Table
The next table I want to create is the Ribbits table. This table should have four
fields: id , user_id , ribbit and created_at . The SQL code for this table is:
Next, the Follows table. This simply holds the id s of both the follower and followee:
Follows Table
Finally, we have a table, called UserAuth . This holds the user's username and
password hash. I opted not to use the user's ID, because the program already stores
the username, when logging in and signing up (the two times when entries are added
to this table), but the program would need to make an extra call to get the user's ID
number. Extra calls mean more latency, so I chose not to use the user's ID.
In a real world project, you may want to add another field like 'hash2' or 'secret'. If all
you need to authenticate a user is one hash, then an attacker only has to guess that
one hash. For example: I randomly enter characters into the hash field in the cookie. If
there are enough users, it might just match someone. But if you have to guess and
match two hashes, then the chance of someone guessing the correct pair drops
exponentially (the same applies to adding three, etc). But to keep things simple, I will
only have one hash.
Here's the SQL code:
UserAuth Table
Now that we have all the tables setup, you should have a pretty good idea of how the
overall site will work. We can start writing the Model class in our MVC framework.
The Model
Create a file, called model.php and enter the following class declaration:
1class Model{
2
3 private $db; // Holds mysqli Variable
4
5 function __construct(){
6 $this->db = new mysqli('localhost', 'user', 'pass', 'Ribbit');
7 }
8}
This looks familiar to you if you have written PHP classes in the past. This code
basically creates a class called Model . It has one private property named $db which
holds a mysqli object. Inside the constructor, I initialized the $db property using the
connection info to my database. The parameter order is: address, username,
password and database name.
Before we get into any page-specific code, I want
to create a few low-level commands that abstract
the common mySQL functions
like SELECT and INSERT .
The first function I want to implement is select() . It accepts a string for the table's
name and an array of properties for building the WHERE clause. Here is the entire
function, and it should go right after the constructor:
Here is the completed class's structure; just save this to a file called router.php :
class Router{
01 private $routes;
02
03 function __construct(){
04
$this->routes = array();
05
06 }
07
08 public function lookup($query)
09 {
10 if(array_key_exists($query, $this->routes))
11 {
12 return $this->routes[$query];
13 }
14 else
15
{
16
17 return false;
18 }
19 }
}
This class has one private property called routes , which is the "phone book" for our
controllers. There's also a simple function called lookup() , which returns a string if the
path exists in the routes property. To save time, I will list the ten functions that our
controller will have:
01function __construct(){
02 $this->routes = array(
03 "home" => "indexPage",
04 "signup" => "signUp",
05 "login" => "login",
06 "buddies" => "buddies",
07
"ribbit" => "newRibbit",
08
"logout" => "logout",
09
10 "public" => "publicPage",
11 "profiles" => "profiles",
12 "unfollow" => "unfollow",
13 "follow" => "follow"
14 );
}
The list goes by the format of 'url' => 'function name' . For example, if someone goes
to ribbit.com/home , then the router tells the controller to execute
the indexPage() function.
The router is only half the solution; we need to tell Apache to redirect all traffic to the
controller. We'll achieve this by creating a file called .htaccess in the root directory of
the site and adding the following to the file:
1RewriteEngine On
2RewriteRule ^/?Resource/(.*)$ /$1 [L]
3RewriteRule ^$ /home [redirect]
4RewriteRule ^([a-zA-Z]+)/?([a-zA-Z0-9/]*)$ /app.php?page=$1&query=$2 [L]
This may seem a little intimidating if you've never used apache's mod_rewrite. But
don't worry; I'll walk you through it line by line.
The first line tells Apache to enable mod_rewrite; the remaining lines are the rewrite
rules. With mod_rewrite, you can take an incoming request with a certain URL and
pass the request onto a different file. In our case, we want all requests to be handled
by a single file so that we can process them with the controller. The mod_rewrite
module also lets us have URLs like ribbit.com/profile/username instead
of ribbit.com/profile.php?username=username --making the overall feel of your app more
professional.
I said, we want all requests to go to a single file, but that's really not accurate. We
want Apache to normally handle requests for resources like images, CSS files, etc.
The first rewrite rule tells Apache to handle requests that start with Resource/ in a
regular fashion. It's a regular expression that takes everything after the
word Resource/ (notice the grouping brackets) and uses it as the real URL to the file.
So for example: the link ribbit.com/Resource/css/main.css loads the file located
at ribbit.com/css/main.css .
The last rule is the one we came for; it takes all requests (other than those that start
with Resource/ ) and sends them to a PHP file called app.php . That is the file that
loads the controller and runs the whole application.
The " ^ " symbol represents the beginning of the string and the " $ " represents the
end. So the regular expression can be translated into English as: "Take everything
from the beginning of the URL until the first slash, and put it in group 1. Then take
everything after the slash, and put it in group 2. Finally, pass the link to Apache as if it
said app.php?page=group1&query=group2 ." The " [L] " that is in the first and third line
tells Apache to stop after that line. So if the request is a resource URL, it shouldn't
continue to the next rule; it should break after the first one.
I hope all that made sense; the following picture better illustrates what's going on.
If you are still unclear on the actual regular expression, then we have a very nice
article that you can read.
Now that we have everything setup URL-wise, let's create the controller.
The Controller
The controller is where most of the magic happens; all the other pieces of the app,
including the model and router, connect through here. Let's begin by creating a file
called controller.php and enter in the following:
require("model.php");
require("router.php");
01
02class Controller{
03
04
private $model;
05
06 private $router;
07
08 //Constructor
09 function __construct(){
10 //initialize private variables
11 $this->model = new Model();
12 $this->router = new Router();
13
14
//Proccess Query String
15
16 $queryParams = false;
17 if(strlen($_GET['query']) > 0)
18 {
19 $queryParams = explode("/", $_GET['query']);
20 }
21
22 $page = $_GET['page'];
23
24
25 //Handle Page Load
26 $endpoint = $this->router->lookup($page);
27 if($endpoint === false)
28 {
29 header("HTTP/1.0 404 Not Found");
30 }
31 else
32 {
33 $this->$endpoint($queryParams);
34
35
}
}
With mod_rewrite, you can take an incoming request with a
certain URL and pass the request onto a different file.
We first load our model and router files, and we then create a class called Controller .
It has two private variables: one for the model and one for the router. Inside the
constructor, we initialize these variables and process the query string.
If you remember, the query can contain multiple values (we wrote in the .htaccess file
that everything after the first slash gets put in the query--this includes all slashes that
may follow). So we split the query string by slashes, allowing us to pass multiple
query parameters if needed.
Next, we pass whatever was in the $page variable to the router to determine the
function to execute. If the router returns a string, then we will call the specified
function and pass it the query parameters. If the router returns false , the controller
sends the 404 status code. You can redirect the page to a custom 404 view if you so
desire, but I'll keep things simple.
The framework is starting to take shape; you can now call a specific function based
on a URL. The next step is to add a few functions to the controller class to take care
of the lower-level tasks, such as loading a view and redirecting the page.
The first function simply redirects the browser to a different page. We do this a lot, so
it's a good idea to make a function for it:
There is one last function that we need to implement which is required on all pages:
the checkAuth() function. This function will check if a user is signed in, and if so, pass
the user's data to the page. Otherwise, it returns false. Here is the function:
We first check whether or not the Auth cookie is set. This is where the hash we talked
about earlier will be placed. If the cookie exists, then the function tries to verify it with
the database, returning either the user on a successful match or false if it's not in the
table.
That's all we have to do in this class for now. Let's go back into the controller.php file
and implement the Flash class.
For example: if an unauthenticated user tries to post something, the app displays a
message similar to, "You have to be signed in to perform that action." There are
different kinds of flashes: error, warning and notice, and I decided it is easier to create
a Flash class instead of passing multiple variables (like msg and type . Additionally,
the class will have the ability to output a flash's HTML.
Here is the complete Flash class, you can add this to controller.php before
the Controller class definition:
class Flash{
01
02 public $msg;
03 public $type;
04
05 function __construct($msg, $type)
06 {
07
$this->msg = $msg;
08
09 $this->type = $type;
10 }
11
12 public function display(){
13 echo "<div class=\"flash " . $this->type . "\">" . $this->msg .
14"</div>";
15 }
}
This class is straight-forward. It has two properties and a function to output the
flash's HTML.
We now have all the pieces needed to start displaying pages, so let's create
the app.php file. Create the file and insert the following code:
1<?php
2 require("controller.php");
3 $app = new Controller();
And that's it! The controller reads the request from the GET variable, passes it to the
router, and calls the appropriate function. Let's create some of the views to finally get
something displayed in the browser.
The Views
Create a folder in the root of your site called Views . As you may have already
guessed, this directory will contains all the actual views. If you are unfamiliar with the
concept of a view, you can think of them as files that generate pieces of HTML that
build the page. Basically, we'll have a view for the header, footer and one for each
page. These pieces combine into the final result (i.e. header + page_view + footer =
final_page).
Let's start with the footer; it is just standard HTML. Create a file
called footer.php inside the Views folder and add the following HTML:
</div>
1 </div>
2 <footer>
3 <div class="wrapper">
4 Ribbit - A Twitter Clone Tutorial<img
5src="http://cdn.tutsplus.com/net.tutsplus.com/authors/jeremymcpeak//Resource/gfx/logo-
6nettuts.png">
7 </div>
8 </footer>
9</body>
</html>
I think this demonstrates two things very well:
• To access the images that are in the gfx folder, I added Resources/ to the
beginning of the path (for the mod_rewrite rule).
Next, let's create the header.php file. The header is a bit more complicated because it
must determine if the user is signed in. If the user is logged in, it displays the menu
bar; otherwise, it displays a login form. Here is the complete header.php file:
01<!DOCTYPE html>
02<html>
03 <head>
04 <link rel="stylesheet/less" href="/Resource/style.less">
05 <script src="/Resource/less.js"></script>
06 </head>
07 <body>
08
<header>
09
<div class="wrapper">
10
11 <img
12src="http://cdn.tutsplus.com/net.tutsplus.com/authors/jeremymcpeak//Resource/gfx/logo.png"
13>
14 <span>Twitter Clone</span>
15 <?php if($User !== false){ ?>
16 <nav>
17 <a href="/buddies">Your Buddies</a>
18 <a href="/public">Public Ribbits</a>
19 <a href="/profiles">Profiles</a>
20 </nav>
21 <form action="/logout" method="get">
22
<input type="submit" id="btnLogOut" value="Log Out">
23
</form>
24
25 <?php }else{ ?>
26 <form method="post" action="/login">
27 <input name="username" type="text"
28placeholder="username">
29 <input name="password" type="password"
placeholder="password">
<input type="submit" id="btnLogIn" value="Log In">
</form>
30 <?php } ?>
31 </div>
</header>
<div id="content">
<div class="wrapper">
I'm not going to explain much of the HTML. Overall, this view loads in the CSS style
sheet and builds the correct header based on the user's authentication status. This is
accomplished with a simple if statement and the variable passed from the controller.
The last view for the homepage is the actual home.php view. This view contains the
greeting picture and signup form. Here is the code for home.php :
<img
src="http://cdn.tutsplus.com/net.tutsplus.com/authors/jeremymcpeak//Resource/gfx/frog.jpg"
01
>
02
<div class="panel right">
03
<h1>New to Ribbit?</h1>
04
05 <p>
06 <form action="/signup" method="post">
07 <input name="email" type="text" placeholder="Email">
08 <input name="username" type="text" placeholder="Username">
09 <input name="name" type="text" placeholder="Full Name">
10 <input name="password" type="password" placeholder="Password">
11 <input name="password2" type="password" placeholder="Confirm Password">
12 <input type="submit" value="Create Account">
13 </form>
14 </p>
</div>
Together, these three views complete the homepage. Now let's go write the function
for the home page.
At this point if you have everything setup correctly (i.e. Apache and our code so far),
then you should be able to go to the root of your site (e.g. localhost) and see the
home page.
Congratulations!! It's smooth sailing from here on out... well at least smoother sailing.
It's just a matter of repeating the previous steps for the other nine functions that we
defined in the router.
Those checks need to happen in the Model class because we need a connection to
the database. Let's go back to the Model class and implement
the signupUser() function. You should put this right after the userForAuth() function:
public function signupUser($user){
$emailCheck = $this->exists("Users", array("email" => $user['email']));
01
02
03 if($emailCheck){
04 return 1;
05 }
06 else {
07 $userCheck = $this->exists("Users", array("username" =>
08$user['username']));
09
10
if($userCheck){
11
return 2;
12
13 }
14 else{
15 $user['created_at'] = date( 'Y-m-d H:i:s');
16 $user['gravatar_hash'] = md5(strtolower(trim($user['email'])));
17 $this->insert("Users", $user);
18 $this->authorizeUser($user);
19 return true;
20 }
21 }
}
We use our exists() function to check the provided email or username, returning an
error code either already exists. If everything passes, then we add the final few
fields, created_at and gravatar_hash , and insert them into the database.
Before returning true , we authorize the user. This function adds the Auth cookie and
inserts the credentials into the UserAuth database. Let's add
the authorizeUser() function now:
This function builds the unique hash for a user on sign up and login. This isn't a very
secure method of generating hashes, but I combine the sha1 hash of the username
along with twelve random alphanumeric characters to keep things simple.
functions to erase the cookie and remove the entry from the database.
Let's jump over to the Model class and add the necessary functions for these to
updates. The first is attemptLogin() which tries to login and returns true or false . Then
we have logoutUser() :
public function attemptLogin($userInfo){
01 if($this->exists("Users", $userInfo)){
02 $this->authorizeUser($userInfo);
03 return true;
04 }
05 else{
06
return false;
07
08 }
09}
10
11public function logoutUser($hash){
12 $this->delete("UserAuth", array("hash" =>
13$hash));
14 setcookie ("Auth", "", time() - 3600);
}
Hang with me; we are getting close to the end! Let's build the "Buddies" page. This
page contains your profile information and a list of posts from you and the people
you follow. Let's start with the actual view, so create a file called buddies.php in
the Views folder and insert the following:
We have three possible flashes here: one for signup, one for login and one for if the
user exceeds the 140 character limit on a new ribbit. Finally, we call
the loadPage() function to display everything.
Now in the Model class we have to enter the two functions we called above. First we
have the 'getUserInfo' function:
You should now be able to signup, login and view the buddies page. We are still not
able to create ribbits so let's get on that next.
For the ribbits just create a file called public.php and put the following inside:
Then, we take the results and combine them with the user's information from the first
query. All this data is returned to display in the profiles view.
If you have done everything correctly, you should be able to traverse through all the
pages and post ribbits. The only thing we have left to do is add the functions to follow
and unfollow other people.
Tying up the Loose Ends
There is no view associated with these functions, so these will be quick. Let's start
with the functions in the Controller class:
private function follow($params){
01 $user = $this->checkAuth();
02
if($user === false){ $this->redirect("home/7"); }
03
04 else{
05 $this->model->follow($user, $params[0]);
06 $this->redirect("profiles");
07 }
08}
09
10private function unfollow($params){
11 $user = $this->checkAuth();
12 if($user === false){ $this->redirect("home/7"); }
13 else{
14 $this->model->unfollow($user, $params[0]);
15
$this->redirect("profiles");
16
}
17
}
These functions, as you can probably see, are almost identical. The only difference is
that one adds a record to the Follows table and one removes a record. Now let's finish
it up with the functions in the Model class:
The site is now fully operational!!! The last thing which I want to add is
another .htaccess file inside the Views folder. Here are its contents:
1Order allow,deny
2Deny from all
This is not strictly necessary, but it is good to restrict access to private files.
Conclusion
We definitely built a Twitter clone from scratch!
This has been a very long article, but we covered a lot! We setup a database and
created our very own MVC framework. We definitely built a Twitter clone from
scratch!
Please note that, due to length restraints, I had to omit a lot of the features that you
might find in a real production application, such as Ajax, protection against SQL
injection, and a character counter for the Ribbit box (probably a lot of other things as
well). That said, overall, I think we accomplished a great deal!
I hope you enjoyed this article, feel free to leave me a comment if you have any
thoughts or questions. Thank you for reading!
Advertisement
Gabriel Manricks
I'm a freelance web developer with experience spanning the full stack of application
development and a senior writer here at NetTuts+. Besides for that I spend my time writing
books for Packt or working on open source projects I find intriguing . You can find me on Twitter
@gabrielmanricks or visit my site to see all the things I'm working on gabrielmanricks.com.