Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 7f6d6800c4f4bb65ff0789927b9aa80fbf4b47e4 0 parents
@tboronczyk tboronczyk authored
2  README
@@ -0,0 +1,2 @@
+This is the source code for the article Building Your Own URL Shortener by
+Alex Fraundorf available at http://phpmaster.com/building-your-own-url-shortener
347 include/ShortUrl.php
@@ -0,0 +1,347 @@
+<?php
+/**
+ * This class provides all of the functionality needed to create (encode) and
+ * decode shortened URLs.
+ *
+ * @author Alex Fraundorf - AffordableWebSitePublishing.com LLC
+ *
+ * Give credit where it is due: Although this package is significantly
+ * different, it was orignally inspired by the URL shortener written by
+ * Brian Cray - http://briancray.com/posts/free-php-url-shortener-script
+ */
+class ShortUrl
+{
+ /**
+ * @var string the characters used in building the short URL
+ *
+ * YOU MUST NOT CHANGE THESE ONCE YOU START CREATING SHORTENED URLs!
+ */
+ protected static $chars = "123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ";
+
+ /**
+ * @var string holds the name of the database table to use
+ */
+ protected static $table = "short_urls";
+
+ /**
+ * @var boolean whether to connect to a URL to check if it exists
+ */
+ protected static $checkUrlExists = true;
+
+ /**
+ * @var object holds a reference to a PDO object
+ */
+ protected $pdo;
+
+ /**
+ * @var integer holds the current timestamp
+ */
+ protected $timestamp;
+
+ /**
+ * Constructor
+ *
+ * Sets the class variable $pdo with a reference to a PDO object and the
+ * class variable $timestamp with the current timestamp.
+ *
+ * @param PDO $pdo a reference to the PDO database connection object
+ */
+ public function __construct(PDO $pdo) {
+ $this->pdo = $pdo;
+ $this->timestamp = $_SERVER["REQUEST_TIME"];
+ }
+
+ /**
+ * Create a short code from a long URL.
+ *
+ * Delegates validating the URLs format, validating it, optionally
+ * connecting to the URL to make sure it exists, and checking the database
+ * to see if the URL is already there. If so, the cooresponding short code
+ * is returned. Otherwise, createShortCode() is called to handle the tasks.
+ *
+ * @param string $url the long URL to be shortened
+ * @return string the short code on success
+ * @throws Exception if an error occurs
+ */
+ 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;
+ }
+
+ /**
+ * Retrieve a long URL from a short code.
+ *
+ * Deligates validating the supplied short code, getting the long URL from
+ * the database, and optionally incrementing the URL's access counter.
+ *
+ * @param string $code the short code associated with a long URL
+ * @param boolean $increment whether to increment the record's counter
+ *
+ * @return string the long URL
+ * @throws Exception if an error occurs
+ */
+ 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"];
+ }
+
+ /**
+ * Check to see if the supplied URL is a valid format
+ *
+ * @param string $url the long URL
+ * @return boolean whether URL is a valid format
+ */
+ protected function validateUrlFormat($url) {
+ return filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_HOST_REQUIRED);
+ }
+
+ /* Check to see if the URL exists
+ *
+ * Uses cURL to access the URL and make sure a 404 error is not returned.
+ *
+ * @param string $url the long URL
+ * @return boolean whether the URL does not return a 404 code
+ */
+ 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);
+ }
+
+ /**
+ * Check the database for the long URL.
+ *
+ * If the URL is already in the database then the short code for it is
+ * returned. If the URL is not, false is returned. An exception is thrown
+ * if there is a database error.
+ *
+ * @param string $url the long URL
+ * @return string|boolean the short code if it exists - false if it does not
+ * @throws PDOException if a database exception occurs
+ */
+ 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"];
+ }
+
+ /**
+ * Delegates creating a short code from a long URL.
+ *
+ * Delegates inserting the URL into the database, converting the integer
+ * of the row's ID column into a short code, and updating the database with
+ * the code. If successful, it returns the short code. If there is an error,
+ * an exception is thrown.
+ *
+ * @param string $url the long URL
+ * @return string the created short code
+ * @throws Exception if an error occurs
+ */
+ protected function createShortCode($url) {
+ $id = $this->insertUrlInDb($url);
+ $shortCode = $this->convertIntToShortCode($id);
+ $this->insertShortCodeInDb($id, $shortCode);
+ return $shortCode;
+ }
+
+ /**
+ * Inserts a new row into the database.
+ *
+ * Inserts a new row into the database with the URL and returns the ID of
+ * the row. If there is a database error an exception is thrown.
+ *
+ * @param string $url the long URL
+ * @return integer on success the integer of the newly inserted row
+ * @throws PDOException if an error occurs
+ */
+ protected function insertUrlInDb($url) {
+ $query = "INSERT INTO " . self::$table .
+ " (long_url, date_created) " .
+ " VALUES (:long_url, :timestamp)";
+ $stmnt = $this->pdo->prepare($query);
+ $params = array(
+ "long_url" => $url,
+ "timestamp" => $this->timestamp
+ );
+ $stmnt->execute($params);
+
+ return $this->pdo->lastInsertId();
+ }
+
+ /**
+ * Convert an integer to a short code.
+ *
+ * This method does the actual conversion of the ID integer to a short code.
+ * If successful, it returns the created code. If there is an error, an
+ * exception is thrown.
+ *
+ * @param int $id the integer to be converted
+ * @return string the created short code
+ * @throws Exception if an error occurs
+ */
+ protected function convertIntToShortCode($id) {
+ $id = intval($id);
+ if ($id < 1) {
+ throw new \Exception(
+ "The ID is not a valid integer");
+ }
+
+ $length = strlen(self::$chars);
+ // make sure length of available characters is at
+ // least a reasonable minimum - there should be at
+ // least 10 characters
+ if ($length < 10) {
+ throw new \Exception("Length of chars is too small");
+ }
+
+ $code = "";
+ while ($id > $length - 1) {
+ // determine the value of the next higher character
+ // in the short code should be and prepend
+ $code = self::$chars[fmod($id, $length)] .
+ $code;
+ // reset $id to remaining value to be converted
+ $id = floor($id / $length);
+ }
+
+ // remaining value of $id is less than the length of
+ // self::$chars
+ $code = self::$chars[$id] . $code;
+
+ return $code;
+ }
+
+ /**
+ * Updates the database row with the short code.
+ *
+ * Updates the row with a specific ID with the provided short code. If
+ * successful, true is returned. An exception is thrown if there is an
+ * error.
+ *
+ * @param int $id the ID of the database row to update
+ * @param string $code the short code to associate with the row
+ * @return boolean on success
+ * @throws Exception if an error occurs
+ */
+ protected function insertShortCodeInDb($id, $code) {
+ if ($id == null || $code == null) {
+ throw new \Exception("Input parameter(s) invalid.");
+ }
+ $query = "UPDATE " . self::$table .
+ " SET short_code = :short_code WHERE id = :id";
+ $stmnt = $this->pdo->prepare($query);
+ $params = array(
+ "short_code" => $code,
+ "id" => $id
+ );
+ $stmnt->execute($params);
+
+ if ($stmnt->rowCount() < 1) {
+ throw new \Exception(
+ "Row was not updated with short code.");
+ }
+
+ return true;
+ }
+
+ /**
+ * Check to see if the supplied short code is a valid format
+ *
+ * @param string $code the short code
+ * @return boolean whether the short code is a valid format
+ */
+ protected function validateShortCode($code) {
+ return preg_match("|[" . self::$chars . "]+|", $code);
+ }
+
+ /**
+ * Get the long URL from the database.
+ *
+ * Retrieve the URL associated with the short code from the database. If
+ * there is an error, an exception is thrown.
+ *
+ * @param string $code the short code to look for in the database
+ * @return string|boolean the long URL or false if it does not exist
+ * @throws PDOException if an error occurs
+ */
+ 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;
+ }
+
+ /**
+ * Increment the record's access count.
+ *
+ * Increment the number of times a short code has been looked up to retrieve
+ * its URL.
+ *
+ * @param integer $id the ID of the row to increment
+ * @throws PDOException if an error occurs
+ */
+ protected function incrementCounter($id) {
+ $query = "UPDATE " . self::$table .
+ " SET counter = counter + 1 WHERE id = :id";
+ $stmt = $this->pdo->prepare($query);
+ $params = array(
+ "id" => $id
+ );
+ $stmt->execute($params);
+ }
+}
8 include/config.php
@@ -0,0 +1,8 @@
+<?php
+define("DB_PDODRIVER", "mysql");
+define("DB_HOST", "localhost");
+define("DB_DATABASE", "mydatabase");
+define("DB_USERNAME", "dbuser");
+define("DB_PASSWORD", "dbpassword");
+
+define("SHORTURL_PREFIX", "http://localhost/r.php?c=");
8 public/error.html
@@ -0,0 +1,8 @@
+<html>
+ <head>
+ <title>URL Shortener</title>
+ </head>
+ <body>
+ <p><strong>Oops! An error occured.</strong></p>
+ </body>
+</html>
12 public/index.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title>URL Shortener</title>
+ </head>
+ <body>
+ <form action="submit.php" method="post">
+ <label for="url">Enter URL (http://example.com):</label>
+ <input type="text" name="url" id="url">
+ <input type="submit" value="Shorten">
+ </form>
+ </body>
+</html>
31 public/r.php
@@ -0,0 +1,31 @@
+<?php
+require_once "../include/config.php";
+require_once "../include/ShortUrl.php";
+
+if (empty($_GET["c"])) {
+ header("Location: shorten.html");
+ exit;
+}
+
+$code = $_GET["c"];
+
+try {
+ $pdo = new PDO(DB_PDODRIVER . ":host=" . DB_HOST . ";dbname=" . DB_DATABASE,
+ DB_USERNAME, DB_PASSWORD);
+}
+catch (\PDOException $e) {
+ header("Location: error.html");
+ exit;
+}
+
+$shortUrl = new ShortUrl($pdo);
+try {
+ $url = $shortUrl->shortCodeToUrl($code);
+ header("HTTP/1.1 301 Moved Permanently");
+ header("Location: " . $url);
+}
+catch (\Exception $e) {
+print_r($e);
+ header("Location: error.html");
+ exit;
+}
38 public/submit.php
@@ -0,0 +1,38 @@
+<?php
+require_once "../include/config.php";
+require_once "../include/ShortUrl.php";
+
+if ($_SERVER["REQUEST_METHOD"] != "POST" || empty($_POST["url"])) {
+ header("Location: shorten.html");
+ exit;
+}
+
+try {
+ $pdo = new PDO(DB_PDODRIVER . ":host=" . DB_HOST . ";dbname=" . DB_DATABASE,
+ DB_USERNAME, DB_PASSWORD);
+}
+catch (\PDOException $e) {
+ header("Location: error.html");
+ exit;
+}
+
+$shortUrl = new ShortUrl($pdo);
+try {
+ $code = $shortUrl->urlToShortCode($_POST["url"]);
+}
+catch (\Exception $e) {
+ header("Location: error.html");
+ exit;
+}
+$url = SHORTURL_PREFIX . $code;
+
+echo <<<ENDHTML
+<html>
+ <head>
+ <title>URL Shortener</title>
+ </head>
+ <body>
+ <p><strong>Short URL:</strong> <a href="$url">$url</a></p>
+ </body>
+</html>
+ENDHTML;
12 sql/schema.sql
@@ -0,0 +1,12 @@
+CREATE TABLE IF NOT EXISTS short_urls (
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
+ long_url VARCHAR(255) NOT NULL,
+ short_code VARCHAR(6)
+ CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
+ date_created INTEGER UNSIGNED NOT NULL,
+ counter INTEGER UNSIGNED NOT NULL DEFAULT '0',
+
+ PRIMARY KEY (id),
+ KEY short_code (short_code)
+)
+ENGINE=InnoDB;
Please sign in to comment.
Something went wrong with that request. Please try again.