How to build a URL shortener with PHP?
How to build a URL shortener with PHP?

If you've ever shared a link on your social media, you've probably noticed how long and illegible it is. Using a link shortener not only simplifies link sharing but also makes them easier to read. It is interesting to know that the length of a URL or the characters used in it affect the validity of that URL; You may share a secure, hassle-free link, but its length and illegibility make people skeptical and wonder if they can click it or if they can share it. On the other hand, a link that is short and readable will automatically be valid.

In this article, we will teach you how to make a URL shortener in PHP.

Requirements:

To understand this tutorial, you need to be familiar with the web development process and have a good knowledge of PHP and MySQL.

  • Create a table in the database:

We will use the database to control and handle redirection based on shortened links. The following SQL code creates the short_urls table in the MySQL database. We will use this table to store URL information; This information will include LONG URL, SHORT CODE, HITS and record creation time.

CREATE TABLE `short_urls` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `long_url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `short_code` varchar(25) COLLATE utf8_unicode_ci NOT NULL,
 `hits` int(11) NOT NULL,
 `created` datetime NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
  • Shortener Library (Shortener.class.php):

The link shortening class allows you to create short URLs using programming methods, PHP and MySQL. This class uses the PDO Extension to work with the MySQL database, so a PDO object instance is required to set up the Shortener class.

Our static variables will be:

  • chars: Characters allowed for short links (character groups are separated by |)
  • table: Database table for storing URL information and short links.
  • checkUrlExists: Set to TRUE to check if there is a long URL.
  • codeLength: The length of the short URL characters.

The list of our functions will be as follows:

  • construct: Sets the reference and timing of the PDO object.
  • urlToShortCode: Validate URL and create short links.
  • validateUrlFormat: To evaluate the URL format
  • verifyUrlExists: Checks the presence or absence of links using cURL in PHP.
  • urlExistsInDB: Checks if there is a long URL in the database. Returns the short link if present; otherwise returns the FALSE value.
  • createShortCode: Create a shortcode for long URLs and insert long URLs and short links in the database.
  • generateRandomString: Create a random string (shortcode) with the characters specified in the chars variable.
  • insertUrlInDB: Enter the URL information into the database using PDO Extension and MySQL and return the row ID.
  • shortCodeToUrl: Convert shortcode to long URL and enter the number of hits in the database.
  • validateShortCode: Validates short links based on authorized characters.
  • getUrlFromDB: Receives the link based on a short URL from the database.
  • incrementCounter: Increases the number of URL hits in the database for a specific record.
<?php
/** 
 * Class to create short URLs and decode shortened URLs
 * 
 * @author www.maryam-hajireza.ir 
 * @copyright Copyright (c) 2021, www.maryam-hajireza.ir
 * @url https://www.maryam-hajireza.ir
 */ 
class Shortener
{
    protected static $chars = "abcdfghjkmnpqrstvwxyz|ABCDFGHJKLMNPQRSTVWXYZ|0123456789";
    protected static $table = "short_urls";
    protected static $checkUrlExists = false;
    protected static $codeLength = 7;

    protected $pdo;
    protected $timestamp;

    public function __construct(PDO $pdo){
        $this->pdo = $pdo;
        $this->timestamp = date("Y-m-d H:i:s");
    }

    public function urlToShortCode($url){
        if(empty($url)){
            throw new Exception("No URL was supplied.");
        }

        if($this->validateUrlFormat($url) == false){
            throw new Exception("URL does not have a valid format.");
        }

        if(self::$checkUrlExists){
            if (!$this->verifyUrlExists($url)){
                throw new Exception("URL does not appear to exist.");
            }
        }

        $shortCode = $this->urlExistsInDB($url);
        if($shortCode == false){
            $shortCode = $this->createShortCode($url);
        }

        return $shortCode;
    }

    protected function validateUrlFormat($url){
        return filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
    }

    protected function verifyUrlExists($url){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_NOBODY, true);
        curl_setopt($ch,  CURLOPT_RETURNTRANSFER, true);
        curl_exec($ch);
        $response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        return (!empty($response) && $response != 404);
    }

    protected function urlExistsInDB($url){
        $query = "SELECT short_code FROM ".self::$table." WHERE long_url = :long_url LIMIT 1";
        $stmt = $this->pdo->prepare($query);
        $params = array(
            "long_url" => $url
        );
        $stmt->execute($params);

        $result = $stmt->fetch();
        return (empty($result)) ? false : $result["short_code"];
    }

    protected function createShortCode($url){
        $shortCode = $this->generateRandomString(self::$codeLength);
        $id = $this->insertUrlInDB($url, $shortCode);
        return $shortCode;
    }
    
    protected function generateRandomString($length = 6){
        $sets = explode('|', self::$chars);
        $all = '';
        $randString = '';
        foreach($sets as $set){
            $randString .= $set[array_rand(str_split($set))];
            $all .= $set;
        }
        $all = str_split($all);
        for($i = 0; $i < $length - count($sets); $i++){
            $randString .= $all[array_rand($all)];
        }
        $randString = str_shuffle($randString);
        return $randString;
    }

    protected function insertUrlInDB($url, $code){
        $query = "INSERT INTO ".self::$table." (long_url, short_code, created) VALUES (:long_url, :short_code, :timestamp)";
        $stmnt = $this->pdo->prepare($query);
        $params = array(
            "long_url" => $url,
            "short_code" => $code,
            "timestamp" => $this->timestamp
        );
        $stmnt->execute($params);

        return $this->pdo->lastInsertId();
    }
    
    public function shortCodeToUrl($code, $increment = true){
        if(empty($code)) {
            throw new Exception("No short code was supplied.");
        }

        if($this->validateShortCode($code) == false){
            throw new Exception("Short code does not have a valid format.");
        }

        $urlRow = $this->getUrlFromDB($code);
        if(empty($urlRow)){
            throw new Exception("Short code does not appear to exist.");
        }

        if($increment == true){
            $this->incrementCounter($urlRow["id"]);
        }

        return $urlRow["long_url"];
    }

    protected function validateShortCode($code){
        $rawChars = str_replace('|', '', self::$chars);
        return preg_match("|[".$rawChars."]+|", $code);
    }

    protected function getUrlFromDB($code){
        $query = "SELECT id, long_url FROM ".self::$table." WHERE short_code = :short_code LIMIT 1";
        $stmt = $this->pdo->prepare($query);
        $params=array(
            "short_code" => $code
        );
        $stmt->execute($params);

        $result = $stmt->fetch();
        return (empty($result)) ? false : $result;
    }

    protected function incrementCounter($id){
        $query = "UPDATE ".self::$table." SET hits = hits + 1 WHERE id = :id";
        $stmt = $this->pdo->prepare($query);
        $params = array(
            "id" => $id
        );
        $stmt->execute($params);
    }
}
  • Database configuration (dbConfig.php):

In the dbConfig.php file, the PDO is used to select and transfer to the database. The information you need to specify ARE:

dbHost: Database host
dbUsername: Database username
dbPassword: Database password
dbName: The name of the database

<?php
// Database configuration
$dbHost     = "localhost";
$dbUsername = "root";
$dbPassword = "root";
$dbName     = "codexworld";

// Create database connection
try{
    $db = new PDO("mysql:host=$dbHost;dbname=$dbName", $dbUsername, $dbPassword);
}catch(PDOException $e){
    echo "Connection failed: " . $e->getMessage();
}
  • Create short URL with PHP

The following command will create a short link using the Shortener class.

// Include database configuration file
require_once 'dbConfig.php';

// Include URL Shortener library file
require_once 'Shortener.class.php';

// Initialize Shortener class and pass PDO object
$shortener = new Shortener($db);

// Long URL
$longURL = 'https://www.maryam-hajireza.ir/tutorials/php/';

// Prefix of the short URL 
$shortURL_Prefix = 'https://xyz.com/'; // with URL rewrite
$shortURL_Prefix = 'https://xyz.com/?c='; // without URL rewrite

try{
    // Get short code of the URL
    $shortCode = $shortener->urlToShortCode($longURL);
    
    // Create short URL
    $shortURL = $shortURL_Prefix.$shortCode;
    
    // Display short URL
    echo 'Short URL: '.$shortURL;
}catch(Exception $e){
    // Display error
    echo $e->getMessage();
}
  • Redirect to long URL

The following code manages redirection from short URL to main URL.

// Include database configuration file
require_once 'dbConfig.php';

// Include URL Shortener library file
require_once 'Shortener.class.php';

// Initialize Shortener class and pass PDO object
$shortener = new Shortener($db);

// Retrieve short code from URL
$shortCode = $_GET["c"];

try{
    // Get URL by short code
    $url = $shortener->shortCodeToUrl($shortCode);
    
    // Redirect to the original URL
    header("Location: ".$url);
    exit;
}catch(Exception $e){
    // Display error
    echo $e->getMessage();
}
  • Rewrite URL with HTACCESS

If you want to make the URL user-friendly, use HTACCESS with RewriteRule. With mod_rewrite, you can shorten the URL length and make it easier to share.

Create an .htaccess file and add the following code.

<IfModule mod_rewrite.c>
   RewriteEngine On
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteRule ^([a-zA-Z0-9]+)/?$ redirect.php?c=$1 [L] 
</IfModule>

Conclusion:

This Shortener class helps you to easily shorten your URLs using PHP. You can use this library to create link shorteners.

Comments
سعید

سلام و عرض ادب. ضمن تشکر از اشتراک گذاری کدها من کدها رو برسی کردم یه موضوعی رو ندیدم چک کرده باشید و اون این هست که چک نمیشه کد کوتاه جدیدی که ایجاد میشه قبلا ایجاد نشده باشه. البته این احتمال خصوصا در تعداد کم خیلی پایین هست اما برای مصارف بالا (روزانه چندصد هزار کد کوتاه) احتمال ایجاد کد تکراری وجود داره. میخواستم ببینم شما این موضوع رو هم بررسی کردید و من متوجه نشدم یا اینکه این موضوع بررسی نشده. با تشکر از شما