Simple Hashtag System With PHP MySQL (Free Download)

Welcome to a tutorial on how to create a simple hashtag system with PHP and MySQL. Comments, images, videos, audio, or whatever else – One can never get away from hashtags in this age. But just how do we “build hashtags” into a system? Read on for an example!

ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in.

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Firstly, here is the download link to the example code as promised.

 

QUICK NOTES

  • Create a test database and import 1-database.sql.
  • Change the database settings in 2-lib-tag.php to your own.
  • Access 4a-tag.html in the browser.
    • Enter a few test messages with hashtags.
    • Do a search by hashtag.
If you spot a bug, feel free to comment below. I try to answer short questions too, but it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming.

 

SCREENSHOT

 

EXAMPLE CODE DOWNLOAD

Click here to download all the example 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.

 

 

HASHTAGS WITH PHP MYSQL

All right, let us now get into the hashtag example with PHP MySQL.

 

PART 1) THE DATABASE

1A) POSTS TABLE

1-database.sql
-- (A) POSTS
CREATE TABLE `posts` (
  `post_id` bigint(20) NOT NULL,
  `post_txt` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
ALTER TABLE `posts`
  ADD PRIMARY KEY (`post_id`),
  ADD FULLTEXT KEY `post_txt` (`post_txt`);
 
ALTER TABLE `posts`
  MODIFY `post_id` bigint(20) NOT NULL AUTO_INCREMENT;

Everyone has different types of content to add hashtags to – Audio, video, images, comments, posts, articles, etc… So to keep things simple, we will just attach hashtags to dummy posts.

  • post_id Primary key and auto-increment.
  • post_txt The content itself.

 

1B) HASHTAGS TABLE

1-database.sql
-- (B) TAGS
CREATE TABLE `tags` (
  `post_id` bigint(20) NOT NULL,
  `tag` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
ALTER TABLE `tags`
  ADD PRIMARY KEY (`post_id`,`tag`);

Hashtags that are attached to each post.

  • post_id Partial primary key and foreign key.
  • tag Name of the tag.

 

 

PART 2) PHP HASHTAGS LIBRARY

2-lib-tag.php
<?php
class Tag {
  // (A) CONSTRUCTOR - CONNECT TO DATABASE
  private $pdo = null;
  private $stmt = null;
  public $error = "";
  function __construct () { try {
    $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
    ]);
  } catch (Exception $ex) { exit($ex->getMessage()); }}

  // (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) {
    $this->stmt = $this->pdo->prepare($sql);
    $this->stmt->execute($data);
  }

  // (D) SAVE POST + TAGS
  function save ($txt, $tags=null, $id=null) {
    // (D1) ADD/UPDATE POST
    if ($id==null) {
      $this->query("INSERT INTO `posts` (`post_txt`) VALUES (?)", [$txt]);
    } else {
      $this->query("UPDATE `posts` SET `post_txt`=? WHERE `post_id`=?", [$txt, $id]);
    }

    // (D2) DELETE OLD TAGS
    if ($id==null) { $id = $this->pdo->lastInsertId(); }
    else { $this->query("DELETE FROM `tags` WHERE `post_id`=?", [$id]); }

    // (D3) INSERT NEW TAGS
    if ($tags != null) {
      $tags = json_decode($tags, true);
      $sql = "INSERT INTO `tags` (`post_id`, `tag`) VALUES ";
      $data = [];
      foreach ($tags as $t) {
        $sql .= "(?,?),";
        $data[] = $id; $data[] = $t;
      }
      $this->query(substr($sql, 0, -1).";", $data);
    }

    // (D4) DONE
    return true;
  }

  // (E) GET POSTS
  function get ($tag=null) {
    // (E1) GET ALL
    if ($tag==null) { $this->query("SELECT * FROM `posts`"); }

    // (E2) SEARCH BY TAG
    else {
      $sql = "SELECT p.* FROM `tags` `t`
              JOIN `posts` `p` USING (`post_id`)
              WHERE `tag`=?";
      $this->query($sql, [$tag]);
    }

    // (E3) FETCH!
    return $this->stmt->fetchAll();
  }
}

// (F) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8");
define("DB_USER", "root");
define("DB_PASSWORD", "");

// (G) NEW HASH TAG OBJECT
$_TAG = new Tag();

Next, we have a seemingly massive PHP library to work with the database. But keep calm and look carefully.

  • (A, B, G) When $_TAG = new Tag() is created, the constructor connects to the database automatically. The destructor closes the connection.
  • (C) A helper function to run an SQL statement.
  • (D & E) The “actual” hashtag functions.
    • get() Get all posts, or search for posts with the specified tag.
    • save() Add a new post, or update an existing post.
  • (F) Self-explanatory. Change the database settings to your own.

 

 

PART 3) AJAX HANDLER

3-ajax-tag.php
<?php
if (isset($_POST["req"])) {
  require "2-lib-tag.php";
  switch ($_POST["req"]) {
  // (A) INVALID REQUEST
  default: echo "Invalid request"; break;

  // (B) GET POSTS
  case "get":
    echo json_encode($_TAG->get(
      isset($_POST["tag"]) ? $_POST["tag"] : null
    ));
    break;

  // (C) SAVE POST
  case "save":
    echo $_TAG->save(
      $_POST["txt"],
      isset($_POST["tags"]) ? $_POST["tags"] : null,
      isset($_POST["id"]) ? $_POST["id"] : null
    ) ? "OK" : $_TAG->error;
    break;
}}

Of course, the library will not do anything by itself. This is a simple endpoint to accept AJAX requests and how it works is very simple – Just send $_POST["req"] to specify the request, followed by the required parameters.

  • $_POST["req"] = "get" Get all posts. If $_POST["tag"] is provided, will search for all posts that contain the specified tag.
  • $_POST["req"] = "save" Self-explanatory. Save a given post, along with the given hashtags.

 

PART 4) DEMO HASHTAG PAGE

4A) THE HTML

4a-tag.html
<!-- (A) ADD POST -->
<form onsubmit="return htag.save()">
  <input type="text" required id="add"
         placeholder="Post some dummy messages with #hashtags">
  <input type="submit" value="Post">
</form>
 
<!-- (B) SEARCH BY TAG -->
<form onsubmit="return htag.get(true)">
  <input type="text" id="search"
         placeholder="Then search by hashtag">
  <input type="submit" value="Search">
</form>
 
<!-- (C) POSTS -->
<div id="posts"></div>

Well, this is just a dummy page to demonstrate how we can attach hashtags to posts.

  1. An “add post” HTML form.
  2. Search for posts with the given hashtag.
  3. An empty <div> to show the posts.

 

 

4B) JAVASCRIPT INITIALIZE

4b-tag.js
var htag = {
  // (A) INITIALIZE
  hAdd : null, // html add post field
  hSearch : null, // html search field
  hPosts : null, // html posts <div>
  init : () => {
    // (A1) GET HTML ELEMENTS
    htag.hAdd = document.getElementById("add");
    htag.hSearch = document.getElementById("search");
    htag.hPosts = document.getElementById("posts");
 
    // (A2) LOAD POSTS
    htag.get();
  },
  // ...
};
window.onload = htag.init;

On window load, htag.init() will run. Nothing much going on here, just getting some of the related HTML elements.

 

4C) JAVASCRIPT SHOW POSTS

4b-tag.js
// (B) GET/SEARCH POSTS
get : search => {
  // (B1) FORM DATA
  let data = new FormData();
  data.append("req", "get");
  if (search == true) {
    data.append("tag", htag.hSearch.value);
  }
 
  // (B2) FETCH & DRAW HTML
  fetch("3-ajax-tag.php", { method : "post", body : data })
  .then(res => res.json())
  .then(data => {
    htag.hPosts.innerHTML = "";
    data.forEach(post => {
      let row = document.createElement("div");
      row.className = "row";
      row.innerHTML = post["post_txt"];
      htag.hPosts.append(row);
    });
  });
  return false;
},

Remember the AJAX endpoint 3-ajax-tag.php? We are just making a $_POST["req"] = "get" fetch request to get all the posts, and showing them in HTML.

 

4D) JAVASCRIPT SAVE POST

4b-tag.js
// (C) SAVE POST
save : () => {
  // (C1) GET TAGS
  let matches = htag.hAdd.value.match(/(^|\s)(#[a-z\d-]+)/g),
  tags = matches === null ? null : [...new Set(matches)];
  if (tags!=null) {
    tags.forEach((t, i) => { tags[i] = t.substring(2); });
  }
 
  // (C2) FORM DATA
  let data = new FormData();
  data.append("req", "save");
  data.append("txt", htag.hAdd.value);
  if (tags!=null) { data.append("tags", JSON.stringify(tags)); }
 
  // (C3) POST + UPDATE HTML LIST
  fetch("3-ajax-tag.php", { method : "post", body : data })
  .then(res => res.text())
  .then(txt => {
    if (txt == "OK") {
      htag.hAdd.value = "";
      htag.get();
    } else { alert(txt); }
  });
 
  // (C4) DONE
  return false;
}

Finally, this one is just long-winded. We are sending $_POST["req"] = "save" to the AJAX handler once again. With the text and tags, of course.

 

 

EXTRA BITS & LINKS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

DUMB. HASHTAGS SHOULD BE PROCESSED WITH PHP.

My developer senses are tingling. I can hear the trolls screaming “it’s so dumb, just use PHP to separate the hashtags from the text”. The reason why I use Javascript to separate the hashtags from the text is simple:

  • Not all people are working with short posts and comments.
  • Some people may prefer to have 2 separate fields.
  • For example, one field for “image caption”, another for “add hashtags to image”.

So yep, it makes sense for the PHP library to process the “content” and “hashtags” separately. If it is better for you to separate the hashtags directly on the server side – By all means, please do so. I am just catering to the “general public” here.

 

COMPATIBILITY CHECKS

This example will work on most modern “grade A” browsers.

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!

Leave a Comment

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