Social Network Friends Relationship System Using PHP and MySQL - Part 1

When building a social network, it is important to think about the relationship design between the users in the site. I have already posted an article on how to design a database to store such informations. The article focused only on the database design aspect and how to implement it. In here we will be creating a Facebook style friends relationship system design using PHP and MySQL entirely.

Read the post on Designing Database for Social Network Friends Relationship if you have not read it previously. It is prerequisite to know some of basic OOP programming in PHP or any other language.


Database Design

The following is the database design for implementing the above system.

Database Schema
Database Schema

The important thing to understand in this design is that the id of user one must always be lesser than user two. The action user id must be the id of the user who performed an action that caused the status to be changed.

Components

The 3 main component for implementing this system is,

  • User - Stores a user details
  • Relationship - Stores details about relationship between two users.
  • Relation - Manages the relationship operations
UML Class Diagram
UML Class Diagram

Both the User and Relationship classes are a Data transfer object (DTO). It uses the DTO design pattern which is mostly a data carrier between process. It mostly contains accessor and mutator methods along with some helper methods.

User.php

<?php
/**
 * Stores the details of a particular user
 * 
 * Copyright (c)  2013-2015 Codedodle.com
 * 
 * @author Tamil Selvan K <info@codedodle.com>
 */
class User {
  
  /**
   * The Unique id of the user
   *
   * @var Int
   */
  private $userId;
  
  /**
   * Name of the user
   *
   * @var String
   */
  private $userName;
  
  /**
   * User email id
   *
   * @var String
   */
  private $email;
  
  /**
   * User password
   *
   * @var String
   */
  private $password;
  
  //##################### Accessor and Mutator Methods #########################
  
  public function getUserId() {
    return $this->userId;
  }
  
  public function setUserId($userId) {
    $this->userId = $userId;
  }
  
  public function getUsername() {
    return $this->userName;
  }
  
  public function setUsername($userName) {
    $this->userName = $userName;
  }
  
  public function getEmail() {
    return $this->email;
  }
  
  public function setEmail($email) {
    $this->email = $email;
  }
  
  public function getPassword() {
    return $this->password;
  }
  
  public function setPassword($password) {
    $this->password = $password;
  }
  
  //##################### End of Accessor and Mutator Methods ##################
  
  /**
   * Returns the User Object provided the id of the user.
   * 
   * @param mysqli $db
   * @param int $id
   * @return \User
   */
  public function getUser($db, $id) {
    $resultObj = $db->query('SELECT * FROM `users` ' . 
                'WHERE `users`.`user_id` = ' . (int) $id);
    $user_details = $resultObj->fetch_assoc();
    $user = new User();
    $user->arrToUser($user_details);
    return $user;
  }
  
  /**
   * Set's the user details returned from the query into the current object.
   * 
   * @param array $userRow
   */
  public function arrToUser($userRow) {
    if (!empty($userRow)) {
      isset($userRow['user_id']) ? 
        $this->setUserId($userRow['user_id']) : '';
      isset($userRow['username']) ? 
        $this->setUsername($userRow['username']) : '';
      isset($userRow['email']) ? 
        $this->setEmail($userRow['email']) : '';
      isset($userRow['password']) ? 
        $this->setPassword($userRow['password']) : '';
    }
  }
}

Relationship.php

<?php
/**
 * This class is store the details of a relationship between two user objects
 * 
 * Copyright (c)  2013-2015 Codedodle.com
 * 
 * @author Tamil Selvan K <info@codedodle.com>
 */
class Relationship {
  
  /**
   * User one in the relationship
   *
   * @var User
   */
  public $userOne;
  
  /**
   * User two in the relationship
   *
   * @var User
   */
  public $userTwo;
  
  /**
   * Determines the status of the relationship
   * 
   * 0 - Pending
   * 1 - Accepted
   * 2 - Declined
   * 3 - Blocked
   * 
   * By default the status is set to 0
   */
  public $status = 0;
  
  /**
   * This is the user who made the most recent status field update
   */
  public $actionUserId;
  
  //##################### Accessor and Mutator Methods #########################
    
  public function getUserOne() {
    return $this->userOne;
  }
  
  public function setUserOne(User $userOne) {
    $this->userOne = $userOne;
  }
  
  public function getUserTwo() {
    return $this->userTwo;
  }
  
  public function setUserTwo(User $userTwo) {
    $this->userTwo = $userTwo;
  }
  
  public function getStatus() {
    return $this->status;
  }
  
  public function setStatus($status) {
    $this->status = $status;
  }
  
  public function getActionUserId() {
    return $this->actionUserId;
  }
  
  public function setActionUserId($actionUserId) {
    $this->actionUserId = $actionUserId;
  }
  
  //##################### End of Accessor and Mutator Methods ##################
  
  /**
   * Set's the details of the relationship from the query result into the 
   * current relationship object instance.
   * 
   * @param array $row
   * @param mysqli $dbCon
   */
  public function arrToRelationship($row, $dbCon) {
    if (!empty($row)) {
      if (isset($row['user_one_id']) && isset($row['user_two_id'])) {
        // Fetch the user details and create the user object set.
        $resultObj = $dbCon->query('SELECT * FROM `users` WHERE `users`.`user_id` IN ('
          . (int)$row['user_one_id'] . ', ' . (int)$row['user_two_id'] . ')');
        
        $usersArr = array();
        while($record = $resultObj->fetch_assoc()) {
          $usersArr[] = $record;
        }
        
        $userOne = new User();
        $userTwo = new User();
        
        // Check which user id is lesser.
        if ($row['user_one_id'] < $row['user_two_id']) {
          $userOne->arrToUser($usersArr[0]);
          $userTwo->arrToUser($usersArr[1]);
        } else {
          $userOne->arrToUser($usersArr[1]);
          $userTwo->arrToUser($usersArr[0]);
        }
        
        $this->setUserOne($userOne);
        $this->setUserTwo($userTwo);
      }
      
      isset($row['status']) ? $this->setStatus((int)$row['status']) : '';
      isset($row['action_user_id']) ? 
        $this->setActionUserId((int)$row['action_user_id']) : '';
    }
  }
}

Relation.php

Relation is the main class that manages most of the operations required for this system. It can be considered to follow the Data access object pattern.

<?php
/**
 * This class manages all the User/Friends Relationship operations.
 * 
 * Copyright (c)  2013-2015 Codedodle.com
 * 
 * @author Tamil Selvan K <info@codedodle.com>
 */
class Relation {
    
  /**
   * The user who is currently logged in
   * 
   * @var User
   */
  private $loggedInUser;
  
  /**
   * Database connection
   * 
   * @var mysqli
   */
  private $dbCon;
  
  /**
   * @param mysqli $dbCon
   * @param User $loggedInUser
   * @return boolean|Relation
   */
  public function __construct($dbCon, User $loggedInUser) {
    if ($dbCon == 'undefined') {
      return false; // or you could throw an exception
    }
    // Current loggedin user
    $this->loggedInUser = $loggedInUser;
    // Database Connection
    $this->dbCon = $dbCon;
  }
    
  /**
   * Return the friend of the current logged in user in the relationship object
   * 
   * @param Relationship $rel
   * @return User $friend
   */
  public function getFriend(Relationship $rel) {
    if ($rel->getUserOne()->getUserId() === $this->loggedInUser->getUserId()) {
      $friend = $rel->getUserTwo();
    } else {
      $friend = $rel->getUserOne();
    }
    
    return $friend;
  }
  
  /**
   * Get all the friends list for the currently loggedin user
   * 
   * @return array Relationship Objects
   */
  public function getFriendsList() {
    $id = (int)$this->loggedInUser->getUserId();
    
    $sql = 'SELECT * FROM `relationship` WHERE ' .
            '(`user_one_id` = ' . $id . ' OR `user_two_id` = '. $id .') ' .
            'AND `status` = 1';
            
    $resultObj = $this->dbCon->query($sql);
    
    $rels = array();
    
    while($row = $resultObj->fetch_assoc()) {
      $rel = new Relationship();
      $rel->arrToRelationship($row, $this->dbCon);
      $rels[] = $rel;
    }
    
    return $rels;
  }
  
  /**
   * Get the list of friend requests sent by the logged in user
   * 
   * @return array Relationship Objects
   */
  public function getSentFriendRequests() {
    $id = (int) $this->loggedInUser->getUserId();
    
    $sql = 'SELECT * FROM `relationship` WHERE ' . 
            '(`user_one_id` = ' . $id . ' OR `user_two_id` = ' . $id . ') ' . 
            'AND `status` = 0 '. 
            'AND `action_user_id` = ' . $id;
            
    $resultObj = $this->dbCon->query($sql);
    
    $rels = array();
    
    while($row = $resultObj->fetch_assoc()) {
      $rel = new Relationship();
      $rel->arrToRelationship($row, $this->dbCon);
      $rels[] = $rel;
    }
    
    return $rels;
  }
  
  /**
   * Get the list of friend requests for the logged in user
   * 
   * @return array Relationship Objects
   */
  public function getFriendRequests() {
    $id = (int) $this->loggedInUser->getUserId();
    
    $sql = 'SELECT * FROM `relationship` ' . 
            'WHERE (`user_one_id` = ' . $id . ' OR `user_two_id` = '. $id .')' . 
            ' AND `status` = 0 ' . 
            'AND `action_user_id` != ' . $id;
    
    $resultObj = $this->dbCon->query($sql);
    
    $rels = array();
    
    while($row = $resultObj->fetch_assoc()) {
      $rel = new Relationship();
      $rel->arrToRelationship($row, $this->dbCon);
      $rels[] = $rel;
    }
    
    return $rels;
  }
  
  /**
   * Get the list of friends blocked by the current user.
   * 
   * @return \Relationship array
   */
  public function getBlockedFriends() {
    $id = (int) $this->loggedInUser->getUserId();
    
    $sql = 'SELECT * FROM `relationship` ' . 
            'WHERE (`user_one_id` = ' . $id . ' OR `user_two_id` = '. $id .')' . 
            ' AND `status` = 3 ' . 
            'AND `action_user_id` = ' . $id;
    
    $resultObj = $this->dbCon->query($sql);
    
    $rels = array();
    
    while($row = $resultObj->fetch_assoc()) {
      $rel = new Relationship();
      $rel->arrToRelationship($row, $this->dbCon);
      $rels[] = $rel;
    }
    
    return $rels;
  }
  
  /**
   * Get the relatiohship for the friend and user
   * 
   * @param User $user
   * @return boolean|int - either false or the relationship status
   */
  public function getRelationship(User $user) {
    $user_one = (int) $this->loggedInUser->getUserId();
    $user_two = (int) $user->getUserId();
    
    if ($user_one > $user_two) {
        $temp = $user_one;
        $user_one = $user_two;
        $user_two = $temp;
    }
    
    $sql = 'SELECT * FROM `relationship` ' .
            'WHERE `user_one_id` = ' . $user_one . 
            ' AND `user_two_id` = ' . $user_two;
    
    $resultObj = $this->dbCon->query($sql);
    
    if ($this->dbCon->affected_rows > 0) {
      $row = $resultObj->fetch_assoc();
      $relationship = new Relationship();
      $relationship->arrToRelationship($row, $this->dbCon);
      return $relationship;
    }
    
    return false;
  }
  
  /**
   * Insert a new friends request
   * 
   * @param User $user - User to which the friend request must be added with.
   * @return Boolean
   */
  public function addFriendRequest(User $user) {
    $user_one = (int) $this->loggedInUser->getUserId();
    $action_user_id = $user_one;
    $user_two = (int) $user->getUserId();
    
    if ($user_one > $user_two) {
        $temp = $user_one;
        $user_one = $user_two;
        $user_two = $temp;
    }
    
    $sql = 'INSERT INTO `relationship` '
            . '(`user_one_id`, `user_two_id`, `status`, `action_user_id`) '
            . 'VALUES '
            . '(' . $user_one . ', '. $user_two .', 0, '. $action_user_id .')';
    
    $this->dbCon->query($sql);
    
    if ($this->dbCon->affected_rows > 0) {
      return true;
    }
    
    return false;
  }
  
  /**
   * Accept a friend request
   * 
   * @param User $user - User to whome the friend request must be accepted with.
   * @return Boolean
   */
  public function acceptFriendRequest(User $user) {
    $user_one = (int) $this->loggedInUser->getUserId();
    $action_user_id = $user_one;
    $user_two = $user->getUserId();
    
    if ($user_one > $user_two) {
      $temp = $user_one;
      $user_one = $user_two;
      $user_two = $temp;
    }
    
    $sql = 'UPDATE `relationship` '
            . 'SET `status` = 1, `action_user_id` = '. $action_user_id 
            .' WHERE `user_one_id` = '. $user_one 
            .' AND `user_two_id` = ' . $user_two;
    
    $this->dbCon->query($sql);
    
    if ($this->dbCon->affected_rows > 0) {
      return true;
    }
    
    return false;
  }
  
  /**
   * Decline a friend request for the user
   * 
   * @params User $user - The user whose request to be declined
   * @return Boolean
   */
  public function declineFriendRequest(User $user) {
    $user_one = (int) $this->loggedInUser->getUserId();
    $action_user_id = $user_one;
    $user_two = $user->getUserId();
    
    if ($user_one > $user_two) {
      $temp = $user_one;
      $user_one = $user_two;
      $user_two = $temp;
    }
    
    $sql = 'UPDATE `relationship` '
            . 'SET `status` = 2, `action_user_id` = '. $action_user_id 
            .' WHERE `user_one_id` = '. $user_one 
            .' AND `user_two_id` = ' . $user_two;
            
    $this->dbCon->query($sql);
    
    if ($this->dbCon->affected_rows > 0) {
      return true;
    }
    
    return false;
  }
  
  /**
   * Cancel a friend request
   * 
   * @param User $user - The friend details
   * @return Boolean
   */
  public function cancelFriendRequest(User $user) {
    $user_one = (int) $this->loggedInUser->getUserId();
    $user_two = (int) $user->getUserId();
    
    if ($user_one > $user_two) {
      $temp = $user_one;
      $user_one = $user_two;
      $user_two = $temp;
    }
    
    $sql = 'DELETE FROM `relationship` ' .
            'WHERE `user_one_id` = ' . $user_one . 
            ' AND `user_two_id` = ' . $user_two .
            ' AND `status` = 0';
    
    $this->dbCon->query($sql);
    
    if ($this->dbCon->affected_rows > 0) {
      return true;
    }
    
    return false;
  }
  
  /**
   * Remove a friend from the friends list
   * 
   * @param User $user - The friend details
   * @return Boolean
   */
  public function unfriend(User $user) {
    $user_one = (int) $this->loggedInUser->getUserId();
    $user_two = (int) $user->getUserId();
    
    if ($user_one > $user_two) {
      $temp = $user_one;
      $user_one = $user_two;
      $user_two = $temp;
    }
    
    $sql = 'DELETE FROM `relationship` ' .
            'WHERE `user_one_id` = ' . $user_one . 
            ' AND `user_two_id` = ' . $user_two .
            ' AND `status` = 1';
    
    $this->dbCon->query($sql);
    
    if ($this->dbCon->affected_rows > 0) {
      return true;
    }
    
    return false;
  }
  
  /**
   * Block a particular user
   * 
   * @param User $user - The user to be blocked
   * @return Boolean
   */
  public function block(User $user) {
    $user_one = (int) $this->loggedInUser->getUserId();
    $action_user_id = $user_one;
    $user_two = $user->getUserId();
    
    if ($user_one > $user_two) {
      $temp = $user_one;
      $user_one = $user_two;
      $user_two = $temp;
    }
    
    $sql = 'UPDATE `relationship` '
            . 'SET `status` = 3, `action_user_id` = '. $action_user_id 
            .' WHERE `user_one_id` = '. $user_one 
            .' AND `user_two_id` = ' . $user_two;
            
    $this->dbCon->query($sql);
    
    if ($this->dbCon->affected_rows > 0) {
      return true;
    }
    
    return false;
  }
  
  /**
   * Unblock a friend who is blocked already.
   * 
   * @param User $user
   * @return boolean
   */
  public function unblockFriend(User $user) {
    $user_one = (int) $this->loggedInUser->getUserId();
    $user_two = (int) $user->getUserId();
    
    if ($user_one > $user_two) {
      $temp = $user_one;
      $user_one = $user_two;
      $user_two = $temp;
    }
    
    $sql = 'DELETE FROM `relationship` ' .
            'WHERE `user_one_id` = ' . $user_one . 
            ' AND `user_two_id` = ' . $user_two .
            ' AND `status` = 3';
    
    $this->dbCon->query($sql);
    
    if ($this->dbCon->affected_rows > 0) {
      return true;
    }
    
    return false;
  }
}

In some of the methods in the above class, the database querying inside the methods can be moved to another method which would avoid repeated querying operation. But still, since this is for explanation i thought it would be more understandable this way for users.

When creating a Relation class we need to pass to the class constructor a Variable containing the Database connection and the User object which has the details of the logged in user. This will be stored in the $dbCon and $loggedInUser property of the class.

Methods

Method params Description
getFriend Relationship $rel

Returns the friend User object based on the logged in user details.

getFriendsList None Get the list of friends of the currently logged in user. Returns an array of Relationship objects.
getSentFriendRequests None Get all the friend requests sent by the logged in user. Returns an array of Relationship objects
getFriendRequests None Retrieve the list of friend requests for the current user. Returns an array of Relationship objects
getBlockedFriends None Get list of users blocked by the current user. Returns an array of Relationship objects
getBlockedFriends None Get list of users blocked by the current user. Returns an array of Relationship objects
getRelationship User $user Get the relationship details between the currently logged in user and the $user provided. Returns an Relationship Object.
addFriendRequest User $user Add a new friend request from the currently logged in user to the $user. Returns true on success and false on failure.
acceptFriendRequest User $user Accept a friend request from another $user to the logged in user. Returns true on success and false on failure.
declineFriendRequest User $user Decline a friend request from another $user to the logged in user. Returns true on success and false on failure.
cancelFriendRequest User $user Cancel a friend request sent by the logged in user to another $user. Returns true on success and false on failure.
unfriend User $user Removes the $user from the friends list of the logged in user. Returns true on success and false on failure.
block User $user Block $user. Returns true on success and false on failure.
unblockFriend User $user Unblock $user. Returns true on success and false on failure.

Read the next part on how to implement the above classes to create a Facebook like Friends relationship system design


Designed by Freepik