Alex Bor - Web Developer

Building An Object-Orientated Testimonials Widget In PHP

thumbnail1

This tutorial shall show you how to build an object-orientated, testimonials widget in the coding language PHP. Testimonials are a great way to show your customers or potential clients what people have said about your products or skills. This tutorial shall show you how to build a testimonials widget using Object-Orientated programming in PHP. This tutorial shall teach object-orientated style programming, creating classes, constructors and MySQLi. This tutorial shall not focus on Bootstrap, though is using it. It also won’t discuss in much details the JS or AJAX calls done with jQuery but they are commented clearly. The tutorial also shall not discuss building a login system, this may be covered in a later tutorial.

Demo or Download

To get started, I shall show the file structure along with some information about what each file shall do.

  • app – The Main System Objects
  • css – The Bootstrap CSS Files
  • js – Bootstrap JS Files
  • index.php – Displaying the Widget

The “app” directory contains all the interesting files, and this tutorial shall focus on this. This folder contains a few files:

  • app.php – Our application interface/controller
  • config.php – The database configurations
  • db.php – Any database interactions (a model)
  • index.php – Our base controller, which handles requests
  • manage.php – Layout template of the admin side to manage testimonials and clients

Configuration

Before we begin, you’ll want to load in the database structure.


CREATE TABLE `clients` (
  `client_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `url` varchar(150) DEFAULT NULL,
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

CREATE TABLE `testimonials` (
  `testimonial_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `text` text,
  `client_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`testimonial_id`),
  KEY `FK_TES_TO_CLI` (`client_id`),
  CONSTRAINT `FK_TES_TO_CLI` FOREIGN KEY (`client_id`) REFERENCES `clients` (`client_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin1;

You’ll notice in the testimonials table we are using a foreign key, this shall link a client to a testimonial. The useful feature of this is having a cascade on the key. That is, if a client gets deleted, all their testimonials shall to, ensuring database integrity. It also prevents adding testimonials to clients that do not actually exist.

The config.php file contains our configuration. This is an array containing connection settings to a MySQL database.

<?php
// app/config.php
return array(
    'host' => 'localhost',
    'username' => 'USERNAME',
    'password' => 'PASSWORD',
    'database' => 'testimonials'
);

?>

The Objects

First, we shall do the database object. This include all functions that interact with the database and shall provide responses back to the caller class or print them out.

<?php
// app/db.php
/*
 *	Author: Alex Bor
 *	URL: https://alexbor.com
 *	This file provided 'as-is' and may be used however.
*/
class Db{

	public $connected = false; //Do we have an active connection?
	private $link = null; //Storing the link to the database

	//get a singular client
	public function getClient($clientId){
		$return = array();

		if($this->connected == false)
			die("ERROR: Not connected to a database");

		$sql = 'SELECT * FROM clients WHERE clients.client_id = ?';

		$stmt = $this->link->prepare($sql);

		//Setting i here, ensure we're passing in an integer. This helps prevent SQL Injection
		$stmt->bind_param('i', $clientId);

		$stmt->execute();

		$res = $stmt->get_result();

		if($res->num_rows == 0)
			return false;

		while($row = $res->fetch_array()) {
  			return $row; //only care about the first item, there only should be one
		}

	}

	//Create a connection to our database
	public function connect($host, $username, $password, $db){
		$this->link = mysqli_connect($host, $username, $password, $db) or die("Error: " . mysqli_error($this->link));
		$this->connected = true;
	}

	//Get all clients from the clients table
	public function getClients(){
		$return = array();

		if($this->connected == false)
			die("ERROR: Not connected to a database");

		$q = 'SELECT * FROM clients';

		$result = mysqli_query($this->link, $q);
		while($row = mysqli_fetch_array($result)){
			$return[] = $row; //add each item to an array
		}

		return $return;
	}

	//add a client to the database
	public function addClient($clientName, $clientURL){
		$responce = array('error' => true, 'message' => 'An error occured');
		$url = strip_tags($clientURL);
		$name = strip_tags($clientName);

		$sql = 'INSERT INTO `clients` (`client_id`, `name`, `url`) VALUES (NULL, ?, ?)';
		$stmt = $this->link->prepare($sql);
		$stmt->bind_param('ss', $name, $url);

		if(!$stmt->execute()):
			$responce['message'] = "Error inserting into the database";
			die(json_encode($responce));
		endif;

		$responce['error'] = false;
		$responce['message'] = "Successefully Inserted";

		die(json_encode($responce));
	}

	//Deleting a client form the database. Due to the key structure on the testimonials site. This will also delete any connected testimonials.
	public function deleteClient($clientId){
		$responce = array('error' => true, 'message' => 'An error occured');
		$client = $this->getClient($clientId);
		if($client == false){
			$responce['message'] = "Client Doesn't Exist!";
			die(json_encode($responce));
		}

		$sql = 'DELETE FROM `clients` WHERE `client_id` = ?';
		$stmt = $this->link->prepare($sql);
		$stmt->bind_param('i', $clientId);

		if(!$stmt->execute()):
			$responce['message'] = "Error deleting from the database";
			die(json_encode($responce));
		endif;

		$responce['error'] = false;
		$responce['message'] = "Successefully Deleted";

		die(json_encode($responce));		

	}

	//Get a singular testimonial
	public function getTestimonial($testimonialId){
		$return = array();

		if($this->connected == false)
			die("ERROR: Not connected to a database");

		$sql = 'SELECT * FROM testimonials WHERE testimonials.testimonial_id = ?';

		$stmt = $this->link->prepare($sql);

		$stmt->bind_param('i', $testimonialId);

		$stmt->execute();

		$res = $stmt->get_result();

		if($res->num_rows == 0)
			return false;

		while($row = $res->fetch_array()) {
  			return $row;
		}
	}

	//get all testimonials
	public function getTestimonials(){
		$return = array();

		if($this->connected == false)
			die("ERROR: Not connected to a database");

		$q = 'SELECT * FROM testimonials
			  JOIN clients ON testimonials.client_id = clients.client_id';

		$result = mysqli_query($this->link, $q);
		while($row = mysqli_fetch_array($result)){
			$return[] = $row;
		}

		return $return;

	}

	//add a testimonial to the database
	public function addTestimonial($clientId, $text){
		$responce = array('error' => true, 'message' => 'An error occured');
		$client = $this->getClient((int) $clientId);
		$testimonial = strip_tags($text);

		//The client must exist, else the integity of the database won't work.
		if($client == false):
			$responce['message'] = 'Client doesn\'t exist!';
			die(json_encode($responce));
		endif;

		$sql = 'INSERT INTO `testimonials` (`testimonial_id`, `client_id`, `text`) VALUES (NULL, ?, ?)';
		$stmt = $this->link->prepare($sql);
		$stmt->bind_param('is', $client['client_id'], $testimonial);

		if(!$stmt->execute()):
			$responce['message'] = "Error inserting into the database";
			die(json_encode($responce));
		endif;

		$responce['error'] = false;
		$responce['message'] = "Successefully Inserted";

		die(json_encode($responce));
	}

	//Deleting a single testimonial
	public function deleteTestimonial($testimonialId){
		$responce = array('error' => true, 'message' => 'An error occured');
		$testimonial = $this->getTestimonial($testimonialId);

		die(print_r($testimonial));

	}

}

Having an object to handle all this makes building the system far easier which shall be shown below.

Next, our App class. This will be used to interact with the database and handle request. It generate the database connection in its constructor. The system is mainly a framework to pass requests to their required functions.


<?php
//app/app.php
require_once('db.php'); //Get our database

class App{

	private $config;
	private $db;

	//Runs whenever that class is created, this shall generate our connection.
	function __construct() {
		$this->config = require_once('config.php');
		$this->db = new Db;
		$this->db->connect($this->config['host'], $this->config['username'], $this->config['password'], $this->config['database']);
		if(!$this->db->connected) die("Error connecting to the database");
	}

	public function addTestimonial($clientId, $text){
		$this->db->addTestimonial($_POST['clientId'], $_POST['text']);
	}

	public function addClient($clientName, $clientURL){
		$this->db->addClient($clientName, $clientURL);
	}

	public function deleteTestimonial($testimonialId){
		$this->db->deleteTestimonial($testimonialId);
	}

	public function deleteClient($clientId){
		$this->db->deleteClient($_POST['clientId']);
	}

	public function getTestimonials(){
		return $this->db->getTestimonials();
		return;
	}

	public function manageTemplate(){
		$clients = $this->db->getClients();
		$testimonials = $this->db->getTestimonials();
		require_once('manage.php');
	}
}

?>

You’ll notice in the function manageTemplate we’re loading up a web page, the following page is what is loaded. It’s just an HTML base to display testimonials and uses jQuery to post methods to the index.php file, which we’ll come to shortly.

<?php 
 // app/manage.php
?>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Manage Testimonials</title>

    <!-- Bootstrap core CSS -->
    <link href="/css/bootstrap.min.css" rel="stylesheet">

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>

  <body>

<div class="container" style="margin-top: 150px;">
	<div class="col-sm-6">
		<a href="#" id="clients" class="btn btn-default btn-block">Clients</a>
	</div>

	<div class="col-sm-6">
		<a href="#" id="testimonials" class="btn btn-default btn-block">Testimonials</a>
	</div>

</div><!-- /.container -->

<div id="clients_block" class="container" style="display: none;">

<table class="table table-striped">
	<thead>
		<tr>
			<th>#</th>
			<th>Name</th>
			<th>URL</th>
			<th>Delete</th>
		</tr>
	</thead>

	<tbody>
		<?php foreach($clients as $c): ?>
		<tr>
			<td><?=$c['client_id']?></td>
			<td><?=$c['name']?></td>
			<td><?=$c['url']?></td>
			<td><a href="#" class="delete_client" data-id='<?=$c['client_id']?>'>X</a></td>
		</tr>
		<?php endforeach; ?>
	</tbody>

</table>
<a href="#" class="btn btn-default btn-block" data-toggle="modal" data-target="#newClient" id="new_testimonial">New Client</a>
</div>

<div id="testimonials_block" class="container" style="display: none;">

<table class="table table-striped">
	<thead>
		<tr>
			<th>#</th>
			<th>Client Name</th>
			<th>Message</th>
			<th>Delete</th>
		</tr>
	</thead>

	<tbody>
		<?php foreach($testimonials as $t): ?>
		<tr>
			<td><?=$t['testimonial_id']?></td>
			<td><?=$t['name']?></td>
			<td><?=$t['text']?></td>
			<td><a href="#" class="delete_testimonial" data-id='<?=$t['testimonial_id']?>'>X</a></td>
		</tr>
		<?php endforeach; ?>
	</tbody>
</table>
<a href="#" class="btn btn-default btn-block" id="new_testimonial" data-toggle="modal" data-target="#newTestimonial">New Testimonial</a>
</div>

<!-- Modal -->
<div class="modal fade" id="newClient" tabindex="-1" role="dialog" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="myModalLabel">New Client</h4>
      </div>
      <div class="modal-body">

		<form>
		  <div class="form-group">
		    <label for="clientName">Client Name</label>
		    <input type="text" id="clientName" name="clientName" class="form-control" />
		  </div>
		  <div class="form-group">
		    <label for="clientURL">Client URL</label>
		    <input type="text" id="clientURL" name="clientURL" class="form-control" />
		  </div>

		</form>

	  </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary" id="addClient">Add Client</button>
      </div>
    </div>
  </div>
</div>

<!-- Modal -->
<div class="modal fade" id="newTestimonial" tabindex="-1" role="dialog" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
        <h4 class="modal-title" id="myModalLabel">New Testimonial</h4>
      </div>
      <div class="modal-body">

		<form>
		  <div class="form-group">
		    <label for="clientForTestimonial">Client</label>

			<select class="form-control" id="clientForTestimonial" name="clientForTestimonial">
				<?php foreach($clients as $c): ?>
				<option value="<?=$c['client_id']?>"><?=$c['name']?></option>
				<?php endforeach; ?>
			</select>

		  </div>
		  <div class="form-group">
		    <label for="testimonialText">Testimonial Text</label>
		    <textarea class="form-control" id="testimonialText" name="testimonialText"></textarea>
		  </div>
		</form>

	  </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary" id="addTestimonial">Add Testimonial</button>
      </div>
    </div>
  </div>
</div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="/js/ie10-viewport-bug-workaround.js"></script>
    <script src="/js/app.js"></script>

  </body>
</html>

The jQuery that goes along side this:

$(document).ready(function($) {

	$( "#clients" ).on( "click", function() {
		$('#clients_block').slideToggle();
	});

	$( "#testimonials" ).on( "click", function() {
		$('#testimonials_block').slideToggle();
	});

	$('#addTestimonial').on('click', function(){
		clientId = $('#clientForTestimonial').val();
		console.log(clientId);

		testimonialText = $('#testimonialText').val();
		console.log(testimonialText);

		$.ajax({
           type: 'POST',
       dataType: 'json',
           data: 'do=addTestimonial&clientId='+clientId+'&text='+testimonialText,
            url: '/app/index.php',
        success: function(json){
		  		if(json.error == true){
		  			alert(json.message);
		  		} else {
		  			alert(json.message);
		  			 location.reload();
		  		}
			}

		});
	});

	$('#addClient').on('click', function(){
		clientName = $('#clientName').val();
		clientURL = $('#clientURL').val();

		$.ajax({
           type: 'POST',
       dataType: 'json',
           data: 'do=addClient&clientName='+clientName+'&clientURL='+clientURL,
            url: '/app/index.php',
        success: function(json){
		  		if(json.error == true){
		  			alert(json.message);
		  		} else {
		  			alert(json.message);
		  			 location.reload();
		  		}
			}

		});
	});

	$('.delete_client').on('click', function(e){
		testId = $(this).attr('data-id');

		$.ajax({
           type: 'POST',
       dataType: 'json',
           data: 'do=deleteClient&clientId='+testId,
            url: '/app/index.php',
        success: function(json){
		  		if(json.error == true){
		  			alert(json.message);
		  		} else {
		  			alert(json.message);
		  			 location.reload();
		  		}
			}

		});
	})

	$('.delete_testimonial').on('click', function(e){
		testId = $(this).attr('data-id');

		$.ajax({
           type: 'POST',
       dataType: 'json',
           data: 'do=deleteTestimonial&testimonialId='+testId,
            url: '/app/index.php',
        success: function(json){
		  		if(json.error == true){
		  			alert(json.message);
		  		} else {
		  			alert(json.message);
		  			 location.reload();
		  		}
			}

		});
	})

})

You can see that this file is pushing data to our index.php file. This index.php file is the base of the whole application. It generates an instance of the application, and handles the request pushing data to where it needs to be.

<?php 
// app/index.php
require_once('app.php'); //Get our application.

$app = new App; //Create a new instance of the application. Due to the constructor, this shall also create the database connection.

//Is data being posted to the script? If so, push the data to the correct location
if(isset($_POST['do'])):
		if($_POST['do'] == 'addTestimonial'){
			$app->addTestimonial($_POST['clientId'], $_POST['text']);
			die();
		}else if($_POST['do'] == 'addClient'){
			$app->addClient($_POST['clientName'], $_POST['clientURL']);
			die();
		}else if($_POST['do'] == 'deleteTestimonial'){
			$app->deleteTestimonial($_POST['testimonialId']);
			die();
		}else if($_POST['do'] == 'deleteClient'){
			$app->deleteClient($_POST['clientId']);
			die();
		}
endif;

//No do methods, so load in the manage template
$app->manageTemplate();
?>

That’s all the complex objects finished! The system is now fully functional. Next, we’ll move on to displaying the testimonials inside a carrousel.

 

Displaying The Widget

The application already has all the methods request to get the widget. After an instance of the application is made, we can get them all calling $app->getTestimonials(). Simple as that! Here’s the index.php file in the root directory for displaying the testimonials.

<?php 

require_once('app/app.php'); //get our application

$app = new App; //create an instance

?>

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Manage Testimonials</title>

    <!-- Bootstrap core CSS -->
    <link href="/css/bootstrap.min.css" rel="stylesheet">
    <link href="http://demo.alexbor.com/global/assets/css/demobase.css" rel='stylesheet'></link>

    <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
      <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>

  <style>

  .testimonial_wrap{
  	background: #d3ebff;
  	border-radius: 6px;
  	padding: 15px 25px;
  	position: relative;
  	margin: 0 10px;
  }

  .triangle{
	border-color: #d3ebff transparent transparent;
    border-style: solid;
    border-width: 25px 12.5px 0;
    bottom: -19px;
    height: 0;
    left: 3px;
    position: absolute;
    width: 0;
	}

	.client_name{
		display: inline-block;
    	margin-top: 14px;
    	margin: 14px 10px 0;
	}

  </style>

  <body>
<div id="demo_header">
Alex Bor - Demo

<span style="float: right;"><a href="https://alexbor.com/blog/building-pgp-enctypted-contact-form">&lt; Back to turorial</a></span>
</div>

<div class="container">
	<h2>Testimonials</h2>
	<div id="testimonials">
		<div id="carousel-example-generic" class="carousel slide" data-ride="carousel">

		  <div class="carousel-inner" role="listbox">
		  <?php
		    		$testimonials = $app->getTestimonials(); //get all the testimonials
		    		$i = 0;
		    		foreach($testimonials as $t):

		    	?>
		    <div class="item <?php if($i == 0){ echo 'active'; $i++;  }?>">

		      <div class="testimonial_wrap"><?= $t['text'] ?> <span class="triangle"></span></div>
		      <span class="client_name"><?= $t['name'] ?> - From <a href="<?= $t['url'] ?>"><?= $t['url'] ?></a></span>
		    </div>
		    <?php endforeach; ?>
		  </div>
		</div>
	</div>

	<a style="margin-top: 50px;" href="/app" class="btn btn-default btn-block">Manage Testimonials</a>
</div>

    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
    <script src="/js/bootstrap.min.js"></script>
    <!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
    <script src="/js/ie10-viewport-bug-workaround.js"></script>
    <script src="/js/testimonials.js"></script>

  </body>
</html>

That’s it all completed. The system shall grab all the testimonials and display them in a carousel.

Conclusion

As a side not, in order to use get_result() you’ll need to have the mysqlnd extension. If using Ubuntu simply install it using apt-get.

sudo apt-get install php5-mysqlnd

Next, add extension=mysqlnd.so to the bottom of your php.ini file and restart your server.

sudo service apache2 restart

This shall install the required drivers and the system shall run successfully, if it wasn’t already installed.

 

Sharing is caring...Tweet about this on TwitterShare on FacebookShare on LinkedInPin on PinterestShare on RedditEmail this to someone