Friend Relationship System In PHP MySQL – Free Source Code

Welcome to a tutorial on how to create a friend system in PHP and MySQL. The social aspect is big these days, and just having the plain Joe users system is not good enough. Adding a friendship system on top is actually not that difficult.

A basic friendship system in PHP and MySQL consists of:

  • A user and relationship database table.
  • PHP library to manage the relationships.
  • Lastly, implementation on the pages themselves.

Well, this might take some time, but let us walk through the development steps in this guide – Read on!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

Download & Notes The Database PHP Library
Friends Page Useful Bits The End

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Create a database and import 1-database.sql.
  • Change the database settings in 2-lib-relation.php to your own.
  • Launch 3a-friends.php in the browser.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

SOURCE CODE DOWNLOAD

Click here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

 

PART 1) THE DATABASE

All right, let us now get started with the database, by creating 2 necessary tables – Users and relations.

 

USERS TABLE

1-databse.php
-- (A) USERS
CREATE TABLE `users` (
  `user_id` int(20) NOT NULL,
  `user_name` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `users`
  ADD PRIMARY KEY (`user_id`),
  ADD KEY `user_name` (`user_name`);

ALTER TABLE `users` CHANGE `user_id` `user_id` INT(20) NOT NULL AUTO_INCREMENT;

This is just a simple dummy user table with 2 fields – The ID and name. Nothing special here, feel free to discard and use your own user table after this tutorial.

 

RELATIONSHIP TABLE

1-database.sql
-- (B) RELATIONS
CREATE TABLE `relation` (
  `from` int(20) NOT NULL,
  `to` int(20) NOT NULL,
  `status` varchar(1) NOT NULL,
  `since` datetime NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

ALTER TABLE `relation`
  ADD PRIMARY KEY (`from`,`to`,`status`),
  ADD KEY `since` (`since`);
  • from and to – The relationship between these two user IDs.
  • status – The current relationship status between the users. I have 3 preset status codes in this tutorial, but feel free to come up with your own as required.
    • P – Pending friend request
    • F – Friends
    • B – Blocked
  • since – Date since the relationship started.

 

 

RELATIONSHIP EXAMPLES

1-database.php
-- (C) DUMMY EXAMPLES
-- (C1) DUMMY USERS
INSERT INTO `users` (`user_id`, `user_name`) VALUES
  (1, 'John Doe'),
  (2, 'Jane Doe'),
  (3, 'Josh Doe'),
  (4, 'Joy Doe');

-- (C2) DUMMY RELATIONS
INSERT INTO `relation` (`from`, `to`, `status`) VALUES

-- "P"ENDING FRIEND REQUEST
-- JOHN SENDS A FRIEND REQUEST TO JANE
(1, 2, 'P'),
-- JOY SENDS A FRIEND REQUEST TO JOHN
(4, 1, 'P'),

-- "F"RIENDS
-- JANE & JOSH ARE FRIENDS
(2, 3, 'F'),
(3, 2, 'F'),
-- JANE & JOY ARE FRIENDS
(2, 4, 'F'),
(4, 2, 'F'),

-- "B"LOCKED
-- JANE FINDS JOSH ANNOYING & BLOCKS HIM
(2, 3, 'B');

Here is some dummy data to help you better understand. But brace yourselves, because the relationships are not exactly straightforward. Let’s start with the possible “actions” first:

  1. Send a friend request.
  2. Accept friend request.
  3. Cancel friend request.
  4. Unfriend.
  5. Block user.
  6. Unblock user.

So now, let’s walk through how to represent these in the database.

  1. As above, if John sends Jane a friend request, it will be from 1 to 2 status "P" (pending).
  2. If Jane accepts the friend request, there will be 2 reciprocal entries – From 1 to 2 status "F", and from 2 to 1 status "F" (friends).
  3. If John decides to cancel the request, we simply delete the pending entry.
  4. Should any party decides to unfriend, both reciprocal entries will be deleted.
  5. Block is a “standalone entry”. Anyone can block anyone, friends or not. For example, John and Jane can be friends, but Jane can still block John – from 2 to 1 status "B" (blocked).
  6. Unblock is as easy as deleting the block entry.

 

 

PART 2) PHP RELATION LIBRARY

Next, we shall move on to the PHP friendship/relationship library to do all the backend heavy lifting.

 

PHP RELATION CLASS

2-lib-relation.php
<?php
class Relation {
  // (A) CONSTRUCTOR - CONNECT TO DATABASE
  private $pdo = null;
  private $stmt = null;
  public $error = "";
  function __construct () {
    try {
      $this->pdo = new PDO(
        "mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET, 
        DB_USER, DB_PASSWORD, [
          PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
          PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
        ]
      );
    } catch (Exception $ex) { exit($ex->getMessage()); }
  }

  // (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
  function __destruct () {
    if ($this->stmt!==null) { $this->stmt = null; }
    if ($this->pdo!==null) { $this->pdo = null; }
  }

  // (C) HELPER FUNCTION - EXECUTE SQL QUERY
  function query ($sql, $data=null) {
    try {
      $this->stmt = $this->pdo->prepare($sql);
      $this->stmt->execute($data);
      return true;
    } catch (Exception $ex) {
      $this->error = $ex->getMessage();
      return false;
    }
  }

  // (D) SEND FRIEND REQUEST
  function request ($from, $to) {
    // (D1) CHECK IF ALREADY FRIENDS
    $this->query(
      "SELECT * FROM `relation` WHERE `from`=? AND `to`=? AND `status`='F'",
      [$from, $to]
    );
    $result = $this->stmt->fetch();
    if (is_array($result)) {
      $this->error = "Already added as friends";
      return false;
    }

    // (D2) CHECK FOR PENDING REQUESTS
    $this->query(
      "SELECT * FROM `relation` WHERE ".
      "(`status`='P' AND `from`=? AND `to`=?) OR ".
      "(`status`='P' AND `from`=? AND `to`=?)",
      [$from, $to, $to, $from]
    );
    $result = $this->stmt->fetch();
    if (is_array($result)) {
      $this->error = "Already has a pending friend request";
      return false;
    }

    // (D3) ADD FRIEND REQUEST
    return $this->query(
      "INSERT INTO `relation` (`from`, `to`, `status`) VALUES (?,?,'P')",
      [$from, $to]
    );
  }

  // (E) ACCEPT FRIEND REQUEST
  function acceptReq ($from, $to) {
    // (E1) UPGRADE STATUS TO "F"RIENDS
    $this->query(
      "UPDATE `relation` SET `status`='F' WHERE `status`='P' AND `from`=? AND `to`=?",
      [$from, $to]
    );
    if ($this->stmt->rowCount()==0) {
      $this->error = "Invalid friend request";
      return false;
    }

    // (E2) ADD RECIPOCAL RELATIONSHIP
    return $this->query(
      "INSERT INTO `relation` (`from`, `to`, `status`) VALUES (?,?,'F')",
      [$to, $from]
    );
  }

  // (F) CANCEL FRIEND REQUEST
  function cancelReq ($from, $to) {
    return $this->query(
      "DELETE FROM `relation` WHERE `status`='P' AND `from`=? AND `to`=?",
      [$from, $to]
    );
  }

  // (G) UNFRIEND
  function unfriend ($from, $to) {
    return $this->query(
      "DELETE FROM `relation` WHERE ".
      "(`status`='F' AND `from`=? AND `to`=?) OR ".
      "(`status`='F' AND `from`=? AND `to`=?)",
      [$from, $to, $to, $from]
    );
  }

  // (H) BLOCK & UNBLOCK
  function block ($from, $to, $blocked=true) {
    // (H1) BLOCK
    if ($blocked) { return $this->query(
      "INSERT INTO `relation` (`from`, `to`, `status`) VALUES (?,?,'B')",
      [$from, $to]
    ); }

    // (H2) UNBLOCK
    else { return $this->query(
      "DELETE FROM `relation` WHERE `from`=? AND `to`=? AND `status`='B'",
      [$from, $to]
    ); }
  }

  // (I) GET FRIEND REQUESTS
  function getReq ($uid) {
    // (I1) GET OUTGOING FRIEND REQUESTS (FROM USER TO OTHER PEOPLE)
    $req = ["in"=>[], "out"=>[]];
    $this->query(
      "SELECT * FROM `relation` WHERE `status`='P' AND `from`=?",
      [$uid]
    );
    while ($row = $this->stmt->fetch()) { $req['out'][$row['to']] = $row['since']; }

    // (I2) GET INCOMING FRIEND REQUESTS (FROM OTHER PEOPLE TO USER)
    $this->query(
      "SELECT * FROM `relation` WHERE `status`='P' AND `to`=?", [$uid]
    );
    while ($row = $this->stmt->fetch()) { $req['in'][$row['from']] = $row['since']; }
    return $req;
  }

  // (J) GET FRIENDS & FOES (BLOCKED)
  function getFriends ($uid) {
    // (J1) GET FRIENDS
    $friends = ["f"=>[], "b"=>[]];
    $this->query(
      "SELECT * FROM `relation` WHERE `status`='F' AND `from`=?", [$uid]
    );
    while ($row = $this->stmt->fetch()) { $friends["f"][$row['to']] = $row['since']; }

    // (J2) GET FOES
    $this->query(
      "SELECT * FROM `relation` WHERE `status`='B' AND `from`=?", [$uid]
    );
    while ($row = $this->stmt->fetch()) { $friends["b"][$row['to']] = $row['since']; }
    return $friends;
  }

  // (K) GET ALL USERS
  function getUsers () {
    $this->query("SELECT * FROM `users`");
    $users = [];
    while ($row = $this->stmt->fetch()) { $users[$row['user_id']] = $row['user_name']; }
    return $users;
  }
}

// (L) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8");
define("DB_USER", "root");
define("DB_PASSWORD", "");

// (M) NEW RELATION OBJECT
$REL = new Relation();

I know this one looks massive, but keep calm and study slowly:

  • (A & B) On creating the $REL = new Relation() object, the constructor will automatically connect to the database. The destructor does the opposite and closes the connection.
  • (C) A simple helper function to run SQL queries.
  • (D to H) Handles the 6 possible “friendship relationship actions” as above.
    • function request() Create an “add friend” request.
    • function acceptReq() Accept the “add friend” request.
    • function cancelReq() Cancel “add friend” request.
    • function unfriend()  Self-explanatory.
    • function block() Block and unblock.
  • (I to K) All the “get” functions here.
    • function getReq() Get all add friend requests for a selected user.
    • function getFriends() Get all friends for the selected user.
    • function getUsers() Fetch all the users.
  • (L & M) Self-explanatory again. Remember to change the database settings.

 

 

PART 3) FRIENDS PAGE

With the foundations in place, all that’s left is to create the “friends page” itself. Here is an example of what can be done.

 

HTML RELATIONSHIP PAGE

3a-friends.php
<?php
// (A) LOAD RELATIOSHIP LIBRARY + SET CURRENT USER
require "2-lib-relation.php";
$uid = 1;
 
// (B) PROCESS RELATIONSHIP REQUEST
if (isset($_POST['req'])) {
  $pass = true;
  switch ($_POST['req']) {
    // (B0) INVALID
    default: $pass = false; $REL->error = "Invalid request"; break;
    // (B1) ADD FRIEND
    case "add": $pass = $REL->request($uid, $_POST['id']); break;
    // (B2) ACCEPT FRIEND
    case "accept": $pass = $REL->acceptReq($_POST['id'], $uid); break;
    // (B3) CANCEL ADD
    case "cancel": $pass = $REL->cancelReq($uid, $_POST['id']); break;
    // (B4) UNFRIEND
    case "unfriend": $pass = $REL->unfriend($uid, $_POST['id'], false); break;
    // (B5) BLOCK
    case "block": $pass = $REL->block($uid, $_POST['id']); break;
    // (B6) UNBLOCK
    case "unblock": $pass = $REL->block($uid, $_POST['id'], false); break;
  }
  echo $pass ? "<div class='ok'>OK</div>" : "<div class='nok'>{$REL->error}</div>";
}
 
// (C) GET + SHOW ALL USERS
$users = $REL->getUsers(); ?>
<div id="userNow">You are now <?=$users[$uid]?>.</div>
<div id="userList"><?php
  $requests = $REL->getReq($uid);
  $friends = $REL->getFriends($uid);
  foreach ($users as $id=>$name) { if ($id != $uid) {
    echo "<div class='urow'>";
    // (C1) USER ID & NAME
    echo "<div class='uname'>$id) $name</div>";
 
    // (C2) BLOCK/UNBLOCK
    if (isset($friends['b'][$id])) {
      echo "<button onclick=\"relate('unblock', $id)\">Unblock</button>";
    } else {
      echo "<button onclick=\"relate('block', $id)\">Block</button>";
    }
 
    // (C3) FRIEND STATUS
    // FRIENDS
    if (isset($friends['f'][$id])) { 
      echo "<button onclick=\"relate('unfriend', $id)\">Unfriend</button>";
    }
    // INCOMING FRIEND REQUEST
    else if (isset($requests['in'][$id])) { 
      echo "<button onclick=\"relate('accept', $id)\">Accept Friend</button>";
    }
    // OUTGOING FRIEND REQUEST
    else if (isset($requests['out'][$id])) { 
      echo "<button onclick=\"relate('cancel', $id)\">Cancel Add</button>";
    }
    // STRANGERS
    else { 
      echo "<button onclick=\"relate('add', $id)\">Add Friend</button>";
    }
    echo "</div>";
  }}
?></div>
 
<!-- (D) NINJA RELATIONSHIP FORM -->
<form id="ninform" method="post" target="_self">
  <input type="hidden" name="req" id="ninreq"/>
  <input type="hidden" name="id" id="ninid"/>
</form>

This is another “quite a massive page”, but let’s walk through section-by-section:

  1. Captain Obvious, load the PHP relation library. In your own system, you should already have a login mechanism. Here, we will just use $uid to assume the currently logged-in user.
  2. This part will only run when the user does any of the 6 “friend or foe” actions.
  3. Gets the list of users, shows the current relationship and possible actions.
  4. A hidden form that will submit when the user clicks on any “friend or foe” button.

Yep, this is actually not-so-bad.

 

 

THE JAVASCRIPT

3b-friends.js
function relate (req, uid) {
  document.getElementById("ninreq").value = req;
  document.getElementById("ninid").value = uid;
  document.getElementById("ninform").submit();
}

Finally, just a small snippet to update and trigger the hidden HTML form.

 

USEFUL BITS & LINKS

That’s all for the code, and here are a few small extras that may be useful to you.

 

WHAT ABOUT BLOCK?

As you may have noticed – Block does nothing in this demo. It’s up to you to decide on your own system. Hide the friend request, ignore messages, don’t show the user, etc…

 

HOW ABOUT REJECTING FRIEND REQUESTS?

Another one that you may have noticed, there are no functions to reject friend requests. This is up to you to decide again.

  • Reject the request and delete the entry? But how do we prevent “add friend” spam this way?
  • Or add another “rejected” entry, where the “add friend” request will be hidden?
  • Penalize users that add spam too quickly?

 

LINKS & REFERENCES

If you don’t have a user system yet, follow up with these.

 

THE END

Thank you for reading, and we have come to the end of this guide. I hope that it has helped you with your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

8 thoughts on “Friend Relationship System In PHP MySQL – Free Source Code”

  1. this is an absolutely terrible way to do it, Running 3 queries to insert a row….Just run 1 insert query and make the friend 1 and 2 columns unique…If it throws an error then they are already friends or have a request pending……

    1. Thanks for sharing! What you are suggesting is not wrong, but let’s explore the human side here – It’s a social system after all. What happens when people see “You have already made a friend request” VS “Violation of PRIMARY KEY constraint”?

      Which is better? Save on a split-second SELECT query for the sake of “performance”, or get tons of “error” and “broken system” reports from users? Cheers and good luck with your project!

      1. I found this all really helpful, thank you. Now I just need to work out a way where instead of showing all the users at once it filters the users by a client-side search.

Leave a Comment

Your email address will not be published.