INTRODUCTION
THE COMMON USEFUL FEATURE
Welcome to a tutorial on how to implement a PHP tag system. Tags are a common feature in systems these days, and you can pretty much see how it is used to describe anything – Posts, products, pictures, videos, audio, etc… If you are looking to put a similar tag system into your own project, this guide will walk you through the exact steps on how to create one. Read on to find out!
I have included a zip file with all the source code at the end of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.
CONFESSION
AN HONEST DISCLOSURE
Quick, hide your wallets! I am an affiliate partner of Google, eBay, Adobe, Bluehost, Clickbank, and more. There are affiliate links and advertisements throughout this website. Whenever you buy things from the evil links that I recommend, I will make a commission. Nah. These are just things to keep the blog going, and allows me to give more good stuff to you guys - for free. So thank you if you decide to pick up my recommendations!
NAVIGATION
TABLE OF CONTENTS
Prelude | Step 1 | Step 2 |
Step 3 | Extra | Closing |
PRELUDE
OVERVIEW & ASSUMPTIONS
Before we hit the code sections, let us first explain the overview of the system, and some of the assumptions that I have made – So you know what to expect from this guide.
ASSUMPTIONS
Most of you guys here should already have an existing system, or a tag system will not make any sense as an independent system. So instead of reinventing the wheel, we shall not touch on how to create posts, products, images, or whatever you want to tag; This guide will purely touch on tags only.
Also, I am going to assume that you guys are comfortable enough with the basics – PHP, SQL, HTML, CSS, Javascript, and more. We will not go into tiny boring little details such as “what is AJAX and JSON”.
OVERVIEW
There will be 3 parts to this guide and system:
- The Database – Where we store all the tags.
- Server-side Scripts – PHP libraries that will do all the process. Save tags to the database, get the tags, etc…
- Client-side Scripts – The user interface, HTML, CSS, and Javascripts.
STEP 1
THE DATABASE
Now that we are done with the overview of the system, let us start with the foundations by building the database tables.
TAGS TABLE
CREATE TABLE `tags` (
`post_id` int(11) NOT NULL,
`tag` varchar(32) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
ALTER TABLE `tags`
ADD PRIMARY KEY (`post_id`,`tag`);
COMMIT;
Field | Description |
post_id | Primary key. The post ID, or it could be your product ID, image ID, whatever you want to tag. |
tag | Primary key. The tag itself, feel free to change the number of characters if you are expecting very long tags. |
Yep, this simple table is actually all we need.
DEMO POSTS TABLE
For the sake of completeness for the beginners, this is an example posts table that we will be adding tags to – But please take note, we can actually tag anything with a proper unique ID.
CREATE TABLE `posts` (
`post_id` int(11) NOT NULL,
`post_title` text NOT NULL,
`post_txt` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `posts` (`post_id`, `post_title`, `post_txt`) VALUES
(1, 'Stream of Rainbow', 'Can the predecessor hope the diesel? The unknown bombs whatever buried manpower. The crucial boy tenders a developed blurb. A top law clicks before a release. Why does our employee monitor the many lawyer? An ear fumes.'),
(2, 'Misty in the Wings', 'When will a competing helmet react in a noise? A paragraph acts above the agenda! A kept delight repairs a controlling crush. Can the procedure vanish? The documented rectangle inconveniences a hysterical luggage. The learned tobacco screams.'),
(3, 'Lord of the Minks', 'The undone complaint collapses past an east estate. The insulting nurse flames the era. A willed hierarchy surfaces. A tentative wife bites the consenting fence.'),
(4, 'Ice in the Scent', 'A futile pump bangs against the cider. A night stomachs a wizard. How does the mania originate? Can a reject wreck a taking battle?');
ALTER TABLE `posts`
ADD PRIMARY KEY (`post_id`);
ALTER TABLE `posts`
MODIFY `post_id` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=5;
COMMIT;
Field | Description |
post_id | The post ID and primary key. |
post_title | The post title. |
post_txt | The contents of the post. |
STEP 2
SERVER-SIDE SCRIPTS
Next, we shall create the server-side scripts to deal with all the backend processes.
CONFIG FILE
The first of the server-side scripts that we have to create is a config file to store all our settings and stuff. Please remember to change the database settings to your own.
DATABASE LIBRARY
pdo = new PDO(
$str, DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
}
// ERROR - CRITICAL STOP - THROW ERROR MESSAGE
catch (Exception $ex) {
print_r($ex);
die();
}
}
function __destruct () {
// __destruct() : close connection when done
if ($this->stmt !== null) { $this->stmt = null; }
if ($this->pdo !== null) { $this->pdo = null; }
}
function exec ($sql, $data=null) {
// exec() : run insert, replace, update, delete query
// PARAM $sql : SQL query
// $data : array of data
try {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
$this->lastID = $this->pdo->lastInsertId();
} catch (Exception $ex) {
$this->error = $ex;
return false;
}
$this->stmt = null;
return true;
}
function start () {
// start() : auto-commit off
$this->pdo->beginTransaction();
}
function end ($commit=1) {
// end() : commit or roll back?
if ($commit) { $this->pdo->commit(); }
else { $this->pdo->rollBack(); }
}
function fetchAll ($sql, $cond=null, $key=null, $value=null) {
// fetchAll() : perform select query (multiple rows expected)
// PARAM $sql : SQL query
// $cond : array of conditions
// $key : sort in this $key=>data order, optional
// $value : $key must be provided. If string provided, sort in $key=>$value order. If function provided, will be a custom sort.
$result = [];
try {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($cond);
// Sort in given order
if (isset($key)) {
if (isset($value)) {
if (is_callable($value)) {
while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
$result[$row[$key]] = $value($row);
}
} else {
while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
$result[$row[$key]] = $row[$value];
}
}
} else {
while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
$result[$row[$key]] = $row;
}
}
}
// No key-value sort order
else {
$result = $this->stmt->fetchAll();
}
} catch (Exception $ex) {
$this->error = $ex;
return false;
}
// Return result
$this->stmt = null;
return count($result)==0 ? false : $result ;
}
function fetch ($sql, $cond=null, $sort=null) {
// fetch() : perform select query (single row expected)
// returns an array of column => value
// PARAM $sql : SQL query
// $cond : array of conditions
// $sort : custom sort function
$result = [];
try {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($cond);
if (is_callable($sort)) {
while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
$result = $sort($row);
}
} else {
while ($row = $this->stmt->fetch(PDO::FETCH_NAMED)) {
$result = $row;
}
}
} catch (Exception $ex) {
$this->error = $ex;
return false;
}
// Return result
$this->stmt = null;
return count($result)==0 ? false : $result ;
}
function fetchCol ($sql, $cond=null) {
// fetchCol() : yet another version of fetch that returns a flat array
// I.E. Good for one column SELECT `col` FROM `table`
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($cond);
$result = $this->stmt->fetchAll(PDO::FETCH_COLUMN, 0);
return count($result)==0 ? false : $result;
}
}
Next, we have a database library here, that will do all the SQL heavy lifting work.
Function | Description |
__construct | The constructor, will automatically connect to the database when the object is created. |
__destruct | The destructor, will automatically close the database connection when the object is destroyed. |
exec | Runs an insert, replace, update, or delete query. |
start | Auto-commit off, used for multiple queries and in conjunction with end. |
end | Commit or rollback? Used in conjunction with start. |
fetchAll | Run a select query, with multiple rows of results expected. |
fetch | Run a select query, with a single row of result expected. Will return an associative array of column > value. |
fetchCol | Run a select query, but only on a single column of data. Will return a flat array of values on the selected column. |
TAG LIBRARY
fetchCol("SELECT `tag` FROM `tags` WHERE `post_id`=?", [$id]);
}
function reTag($id, $tags=null) {
// reTag() : replace tags for the given post ID
// PARAM $id : post ID
// $tags : array of tags
// Auto-commit off
$this->start();
// Remove old tags first
$pass = $this->exec(
"DELETE FROM `tags` WHERE `post_id`=?", [$id]
);
// Add new tags - If any
// Might be a good idea to limit the total number of tags...
if ($pass && is_array($tags) && count($tags)>0) {
$sql = "INSERT INTO `tags` (`post_id`, `tag`) VALUES ";
$data = [];
foreach ($tags as $t) {
$sql .= "(?,?),";
$data[] = $id;
$data[] = $t;
}
$sql = substr($sql, 0, -1);
$pass = $this->exec($sql, $data);
}
// End - commit or rollback
$this->end($pass);
return $pass;
}
}
This is a simple library that will work with the tags.
Function | Description |
getAll | Get all the tags for the given post ID. |
reTag | Delete and replace tags for the given post. |
AJAX HANDLER
0,
"message" => "Invalid request"
]);
break;
// GET TAGS FOR POST
case "get":
$tags = $tagDB->getAll($_POST['post_id']);
echo json_encode([
"status" => is_array($tags) ? 1 : 0,
"message" => $tags
]);
break;
// SAVE TAGS
case "save":
$pass = $tagDB->reTag($_POST['post_id'], json_decode($_POST['tags']));
echo json_encode([
"status" => $pass ? 1 : 0,
"message" => $pass ? "OK" : $tagDB->error
]);
break;
}
Of course, the library themselves will not do anything on their own, and so, we have to create this AJAX handler that will process the user requests. How it works is very simple, we give it a $_POST['req']
to specify which request we want, followed by the required parameters.
Request | Description |
get | Get all the tags for the given post ID. Parameters:
|
save | Save the tags for the given post ID. Parameters:
|
STEP 3
CLIENT-SIDE SCRIPTS
For the final piece of the puzzle, we only have to create the user interface and allow users to tag the posts.
THE HTML
getAll($postID);
// HTML ?>
Simple Tag Demo
Lord of the Minks
The undone complaint collapses past an east estate. The insulting nurse flames the era. A willed hierarchy surfaces. A tentative wife bites the consenting fence.
MANAGE TAGS
%s", $t);
}
}
?>
This is just a simple dummy “edit post” page, and the only important part is the tag docket below, and take note of that hidden post_id
field.
THE CSS
/* [TAGS LIST] */
#tag_dock {
max-width: 400px;
padding: 20px;
background: #f7f7f7;
}
#tag_dock h3 {
margin: 0 0 3px 0;
color: #bf4646;
}
#tag_list {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
#tag_list div.tag {
box-sizing: border-box;
font-size: 0.9em;
width: 30%;
margin: 3px;
padding: 5px;
border: 1px solid #aaa;
background: #e8f2ff;
}
#tag_list div:hover {
background: #ffe8e8;
cursor: pointer;
}
/* [TAGS FORM] */
#tag_dock label, #tag_dock input {
box-sizing: border-box;
display: block;
width: 100%;
margin-top: 8px;
}
#tag_dock label {
font-size: 0.9em;
color: #888;
}
#tag_dock input {
padding: 8px;
font-size: 1em;
}
#tag_dock input[type=button] {
background: #2f75b7;
color: #fff;
cursor: pointer;
border: 0;
}
/* [DOES NOT MATTER] */
html, body {
font-family: arial, sans-serif;
}
Just some cosmetics. Feel free to change this to fit your own website theme.
THE JAVASCRIPT
var tag = {
list : [], // Existing list of tags
add : function (evt) {
// tag.add() : press comma or enter to add tag
if (evt.key=="," || evt.key=="Enter") {
// Input check
var tagged = evt.key=="," ? this.value.slice(0, -1) : this.value,
error = "";
// Freaking joker empty input
if (tagged=="") {
error = "Please enter a valid tag";
}
// Check if already in tags list
if (error=="") {
if (tag.list.indexOf(tagged) != -1) {
error = tagged + " is already defined";
}
}
// OK - Create new tag
if (error=="") {
var newTag = document.createElement("div");
newTag.classList.add("tag");
newTag.innerHTML = tagged;
newTag.addEventListener("click", tag.remove);
document.getElementById("tag_list").appendChild(newTag);
tag.list.push(tagged);
this.value = "";
}
// Not OK - Show error message
else {
this.value = tagged;
alert(error);
}
}
},
remove : function () {
// tag.remove() : remove tag
// Remove tag from list array first
// var pos = tag.list.indexOf(this.innerHTML);
tag.list.splice(tag.list.indexOf(this.innerHTML), 1);
// Remove HTML tag
document.getElementById("tag_list").removeChild(this);
},
save : function () {
// tag.save() : save the tags
// DATA
var data = new FormData();
data.append('req', 'save');
data.append('post_id', document.getElementById('post_id').value);
data.append('tags', JSON.stringify(tag.list));
// AJAX
var xhr = new XMLHttpRequest();
xhr.open('POST', "ajax-tag.php", true);
xhr.onload = function(){
var res = JSON.parse(this.response);
// OK
if (res.status==1) {
alert("Save OK");
} else {
alert(res.message);
}
};
xhr.send(data);
return false;
}
};
// INIT ON WINDOW LOAD
window.addEventListener("load", function() {
// Get list of existing tags
var all = document.querySelectorAll("#tag_list div.tag");
if (all.length>0) {
for (var t of all) {
tag.list.push(t.innerHTML);
// Attach remove listener to tags
t.addEventListener("click", tag.remove);
}
}
// Attach comma listener to input field
document.getElementById("tag_in").addEventListener("keyup", tag.add);
// Enable controls
document.getElementById("tag_in").disabled = false;
document.getElementById("tag_save").disabled = false;
});
Finally, this is the Javascript that will do all the rest of the magic – Take note, this is pure Javascript. No jQuery, no Bootstrap. Feel free to implement your own if you want, or make changes to how this one works.
Function | Description |
tag.add | This is the function that is attached to the |
tag.remove | Removes the given tag from the list. |
tag.save | Proceed with saving the tags list. |
EXTRA
DOWNLOAD & MORE
That’s all for the code, and here is the download link as promised – Plus a small extra that may be useful to you.
SEARCH BY TAG
fetchAll(
"SELECT p.* FROM `tags` t LEFT JOIN posts `p` USING (`post_id`) WHERE t.`tag`=?",
[$tag],
"post_id"
);
}
Now that we have a proper tag system, here is a small extra function that may be useful to you – Searching for all posts that have a given tag.
HOW TO IMPLEMENT
Some of you beginner code ninjas should be confused over how to implement this into your own project now. Remember the 3 steps in this guide, tackle them piece-by-piece and you will do just fine.
- Database – Make sure that the existing table that you want to tag has a unique ID, then create the tags table with that ID as the foreign key.
- Server-side scripts – The PHP libraries and AJAX handlers. Add more of your own functions that are relevant to your project.
- Client-side scripts – Implement the tags docket into your own landing pages, the HTML, CSS, and Javascript.
SOURCE 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.
CLOSING
WHAT’S NEXT?
Thank you for reading, and we have come to the end of this guide. I hope that it has helped you in your project, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!
The post 3 Steps Simple PHP Tag System appeared first on Code Boxx.