Friend Relationship System In PHP MySQL (Free Download)

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

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.

 

SCREENSHOT

 

EXAMPLE 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.

 

 

PHP MYSQL FRIEND RELATIONSHIP

All right, let us now get into the details of how to build a friend relationship system in PHP and MYSQL.

 

PART 1) THE DATABASE

1A) USERS TABLE

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

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

ALTER TABLE `users` CHANGE `user_id` `user_id` BIGINT(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.

 

1B) RELATIONSHIP TABLE

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

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.

 

 

1C) 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. John sends Jane a friend request – 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 “friends” 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

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) {
    $this->stmt = $this->pdo->prepare($sql);
    $this->stmt->execute($data);
  }

  // (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]
    );
    if (is_array($this->stmt->fetch())) {
      $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]
    );
    if (is_array($this->stmt->fetch())) {
      $this->error = "Already has a pending friend request";
      return false;
    }

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

  // (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
    $this->query(
      "INSERT INTO `relation` (`from`, `to`, `status`) VALUES (?, ?, 'F')",
      [$to, $from]
    );
    return true;
  }

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

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

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

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

    // (H3) DONE
    return true;
  }

  // (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, M) On creating $REL = new Relation(), the constructor will automatically connect to the database. The destructor 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()  Unfriend.
    • 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) Self-explanatory. Remember to change the database settings to your own.

 

 

PART 3) FRIENDS PAGE

3A) HTML RELATIONSHIP PAGE

3a-friends.php
<?php
// (A) LOAD RELATIONSHIP LIBRARY + SET CURRENT USER
// @TODO - TIE INTO YOUR OWN USER SYSTEM & SESSION
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(); ?>
<!-- (C1) CURRENT USER -->
<div id="userNow" class="flex">
  <div><img src="funny.png"></div>
  <div>
    <small>you are:</small><br>
    <strong><?=$users[$uid]?></strong>
  </div>
</div>

<!-- (C2) USER LIST -->
<div id="userList"><?php
  $requests = $REL->getReq($uid);
  $friends = $REL->getFriends($uid);
  foreach ($users as $id=>$name) { if ($id != $uid) {
    echo "<div class='uRow flex'>";
    // (C2-1) USER ID & NAME
    echo "<div class='uName'>$id) $name</div>";
 
    // (C2-2) BLOCK/UNBLOCK
    if (isset($friends["b"][$id])) {
      echo "<button onclick=\"relate('unblock', $id)\">Unblock</button>";
    } else {
      echo "<button onclick=\"relate('block', $id)\">Block</button>";
    }
 
    // (C2-3) 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. Here, we will just use $uid to assume the currently logged-in user. In your own system, you should already have a user and login session mechanism – Check out the links below if you do not have an existing user system.
  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.

This is ultimately a dummy HTML demo page only. Feel free to build your own interface however you wish in your own project, use whatever HTML/CSS/JS framework of your choice.

 

 

3B) 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.

 

EXTRA 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!

13 thoughts on “Friend Relationship System In PHP MySQL (Free Download)”

  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 “normal non-technical 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 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.