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!

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

EXAMPLE CODE DOWNLOAD

Source code on GitHub Gist

Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

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, 'Job Doe'),
(2, 'Joe Doe'),
(3, 'Jon Doe'),
(4, 'Joy Doe');

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

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

-- "F"RIENDS
-- JOE & JON ARE FRIENDS
(2, 3, 'F'),
(3, 2, 'F'),
-- JOE & JOY ARE FRIENDS
(2, 4, 'F'),
(4, 2, 'F'),

-- "B"LOCKED
-- JOE FINDS JON 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. Job sends Joe a friend request – from 1 to 2 status "P" (pending).
  2. If Joe 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 Job 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, Joe and Jon can be friends, but Joe can still block Job – 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 () {
    $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
    ]);
  }

  // (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) : void {
    $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", "utf8mb4");
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.

 

EXTRAS

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!

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

    1. Follow up with the user registration and login tutorials in “links & references” above.

Leave a Comment

Your email address will not be published. Required fields are marked *