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.Download SourceLive Demo

Database Design

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

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 statusto 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

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 mutatormethods 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

MethodparamsDescription
getFriendRelationship $relReturns the friend User object based on the logged in user details.
getFriendsListNoneGet the list of friends of the currently logged in user. Returns an array of Relationship objects.
getSentFriendRequestsNoneGet all the friend requests sent by the logged in user. Returns an arrayof Relationship objects
getFriendRequestsNoneRetrieve the list of friend requests for the current user. Returns an arrayof Relationship objects
getBlockedFriendsNoneGet list of users blocked by the current user. Returns an array of Relationship objects
getBlockedFriendsNoneGet list of users blocked by the current user. Returns an array of Relationship objects
getRelationshipUser $userGet the relationship details between the currently logged in user and the $user provided. Returns an Relationship Object.
addFriendRequestUser $userAdd a new friend request from the currently logged in user to the $user. Returns true on success and false on failure.
acceptFriendRequestUser $userAccept a friend request from another $user to the logged in user. Returns true on success and false on failure.
declineFriendRequestUser $userDecline a friend request from another $user to the logged in user. Returns true on success and false on failure.
cancelFriendRequestUser $userCancel a friend request sent by the logged in user to another $user. Returns true on success and false on failure.
unfriendUser $userRemoves the $user from the friends list of the logged in user. Returns true on success and false on failure.
blockUser $userBlock $user. Returns true on success and false on failure.
unblockFriendUser $userUnblock $user. Returns true on success and false on failure.