ShortUrl.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. <?php
  2. class ShortUrl
  3. {
  4. protected static $chars = "123456789bcdfghjkmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ";
  5. protected static $table = "short_urls";
  6. protected static $checkUrlExists = true;
  7. protected $pdo;
  8. protected $timestamp;
  9. public function __construct(PDO $pdo) {
  10. $this->pdo = $pdo;
  11. $this->timestamp = $_SERVER["REQUEST_TIME"];
  12. }
  13. public function urlToShortCode($url) {
  14. if (empty($url)) {
  15. throw new Exception("No URL was supplied.");
  16. }
  17. if ($this->validateUrlFormat($url) == false) {
  18. throw new Exception(
  19. "URL does not have a valid format.");
  20. }
  21. if (self::$checkUrlExists) {
  22. if (!$this->verifyUrlExists($url)) {
  23. throw new Exception(
  24. "URL does not appear to exist.");
  25. }
  26. }
  27. $shortCode = $this->urlExistsInDb($url);
  28. if ($shortCode == false) {
  29. $shortCode = $this->createShortCode($url);
  30. }
  31. return $shortCode;
  32. }
  33. protected function validateUrlFormat($url) {
  34. return filter_var($url, FILTER_VALIDATE_URL,
  35. FILTER_FLAG_HOST_REQUIRED);
  36. }
  37. protected function verifyUrlExists($url) {
  38. $ch = curl_init();
  39. curl_setopt($ch, CURLOPT_URL, $url);
  40. curl_setopt($ch, CURLOPT_NOBODY, true);
  41. curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  42. curl_exec($ch);
  43. $response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
  44. curl_close($ch);
  45. return (!empty($response) && $response != 404);
  46. }
  47. protected function urlExistsInDb($url) {
  48. $query = "SELECT short_code FROM " . self::$table .
  49. " WHERE long_url = :long_url LIMIT 1";
  50. $stmt = $this->pdo->prepare($query);
  51. $params = array(
  52. "long_url" => $url
  53. );
  54. $stmt->execute($params);
  55. $result = $stmt->fetch();
  56. return (empty($result)) ? false : $result["short_code"];
  57. }
  58. protected function createShortCode($url) {
  59. $id = $this->insertUrlInDb($url);
  60. // $shortCode = $this->convertIntToShortCode($id); //Aborted
  61. /* --------------------------New Algorithm Start----------------------------- */
  62. $shortCode = $this->createCode($id, $url); //Accepted
  63. $number = rand(0, 3);
  64. $shortCode = $shortCode[$number];
  65. /* --------------------------New Algorithm End----------------------------- */
  66. $this->insertShortCodeInDb($id, $shortCode);
  67. return $shortCode;
  68. }
  69. protected function insertUrlInDb($url) {
  70. $query = "INSERT INTO " . self::$table .
  71. " (long_url, date_created) " .
  72. " VALUES (:long_url, :timestamp)";
  73. $stmnt = $this->pdo->prepare($query);
  74. $params = array(
  75. "long_url" => $url,
  76. "timestamp" => $this->timestamp
  77. );
  78. $stmnt->execute($params);
  79. return $this->pdo->lastInsertId();
  80. }
  81. /**
  82. * @param unknown $id
  83. * @throws Exception
  84. * @return string
  85. * Aborted
  86. */
  87. protected function convertIntToShortCode($id) {
  88. $id = intval($id);
  89. if ($id < 1) {
  90. throw new Exception(
  91. "The ID is not a valid integer");
  92. }
  93. $length = strlen(self::$chars);
  94. // make sure length of available characters is at
  95. // least a reasonable minimum - there should be at
  96. // least 10 characters
  97. if ($length < 10) {
  98. throw new Exception("Length of chars is too small");
  99. }
  100. $code = "";
  101. while ($id > $length - 1) {
  102. // determine the value of the next higher character
  103. // in the short code should be and prepend
  104. $code = self::$chars[fmod($id, $length)] .
  105. $code;
  106. // reset $id to remaining value to be converted
  107. $id = floor($id / $length);
  108. }
  109. // remaining value of $id is less than the length of
  110. // self::$chars
  111. $code = self::$chars[$id] . $code;
  112. return $code;
  113. }
  114. /**
  115. * @param unknown $id
  116. * @param unknown $url
  117. * @return multitype:string
  118. * Accepted
  119. */
  120. protected function createCode($id, $url){
  121. $base32 = array (
  122. 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
  123. 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
  124. 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
  125. 'y', 'z', '0', '1', '2', '3', '4', '5'
  126. );
  127. $hex = md5($url);
  128. $hexLen = strlen($hex);
  129. $subHexLen = $hexLen / 8;
  130. $output = array();
  131. for ($i = 0; $i < $subHexLen; $i++) {
  132. $subHex = substr ($hex, $i * 8, 8);
  133. $int = 0x3FFFFFFF & (1 * ('0x'.$subHex));
  134. $out = '';
  135. for ($j = 0; $j < 6; $j++) {
  136. $val = 0x0000001F & $int;
  137. $out .= $base32[$val];
  138. $int = $int >> 5;
  139. }
  140. $output[] = $out;
  141. }
  142. return $output;
  143. }
  144. protected function insertShortCodeInDb($id, $code) {
  145. if ($id == null || $code == null) {
  146. throw new Exception("Input parameter(s) invalid.");
  147. }
  148. $query = "UPDATE " . self::$table .
  149. " SET short_code = :short_code, code = :short_code WHERE id = :id";
  150. $stmnt = $this->pdo->prepare($query);
  151. $params = array(
  152. "short_code" => $code,
  153. "id" => $id
  154. );
  155. $stmnt->execute($params);
  156. if ($stmnt->rowCount() < 1) {
  157. throw new Exception(
  158. "Row was not updated with short code.");
  159. }
  160. return true;
  161. }
  162. public function shortCodeToUrl($code, $increment = true) {
  163. if (empty($code)) {
  164. throw new Exception("No short code was supplied.");
  165. }
  166. if ($this->validateShortCode($code) == false) {
  167. throw new Exception(
  168. "Short code does not have a valid format.");
  169. }
  170. $urlRow = $this->getUrlFromDb($code);
  171. if (empty($urlRow)) {
  172. throw new Exception(
  173. "Short code does not appear to exist.");
  174. }
  175. if ($increment == true) {
  176. $this->incrementCounter($urlRow["id"]);
  177. }
  178. return $urlRow["long_url"];
  179. }
  180. protected function validateShortCode($code) {
  181. return preg_match("|[" . self::$chars . "]+|", $code);
  182. }
  183. protected function getUrlFromDb($code) {
  184. $query = "SELECT id, long_url FROM " . self::$table .
  185. " WHERE short_code = :short_code LIMIT 1";
  186. $stmt = $this->pdo->prepare($query);
  187. $params=array(
  188. "short_code" => $code
  189. );
  190. $stmt->execute($params);
  191. $result = $stmt->fetch();
  192. return (empty($result)) ? false : $result;
  193. }
  194. protected function incrementCounter($id) {
  195. $query = "UPDATE " . self::$table .
  196. " SET counter = counter + 1 WHERE id = :id";
  197. $stmt = $this->pdo->prepare($query);
  198. $params = array(
  199. "id" => $id
  200. );
  201. $stmt->execute($params);
  202. }
  203. }