Mit CodeIgniter zum eigenen URL-Shortener (inkl. Statistik)

Kurz-URL-Dienste (bzw. auch URL-Shortener genannt) gibt es im Internet mittlerweile zahlreiche – doch was genau machen sie, und wozu braucht man diese eigentlich?

Diese Dienste bieten, wie der Name erahnen lässt, kurze URLs für beliebige Internetadressen an. Das bedeutet, du übergibst dem jeweiligen Dienstleister eine beliebige Internetadresse, und bekommst im Gegenzug von diesem eine Kurze zurück.

Dies ist insbesondere dann interessant, wenn man Freunden oder Bekannten anstatt einem extrem langen Link, diesen einfach einen Kurzen übermitteln kann. Auch viele soziale Medien beispielsweise limitieren die Anzahl an Zeichen pro Nachricht, und hier erfreuen sich dann solche Kurz-URL-Dienste ebenfalls großer Bedeutung, da sich so gewisse Limits “umgehen” lassen. Der derzeit weltweit am häufigsten genutzte Kurz-URL-Dienst ist  der von den USA aus betriebene Service Bit.ly.

Im Folgenden werde ich euch zeigen, wie einfach ihr euch selbst einen solchen Service basierend auf dem CodeIgniter-Framework bauen könnt:

 

Wie funktioniert ein Kurz-URL-Dienst in der Praxis?

So ziemlich alle Kurz-URL-Anbieter haben deren Service in etwa nach dem gleichen Prinzip aufgebaut:

Auf der Homepage des Anbieters kann man in der Regel in ein Textfeld seine Original-URL angeben, aus welcher dann eine individuelle Kurz-URL generiert wird. Versucht jemand dann diese Kurz-URL zu besuchen, wird man direkt an die jeweilige Ziel-Adresse weitergeleitet, also an jene, welche bei der Generierung dieser URL angegeben wurde. Viele Anbieter stellen zusätzlich auch noch Statistiken über Zugriffe (etc.) zur Verfügung.

 

Das CodeIgniter-Setup

Im folgenden Teil werde ich nun zeigen, wie das Setup mit Codeigniter funktioniert.

 

Grundstruktur vorbereiten

Damit wir mit unserem Kurz-URL-Dienst starten können, brauchen wir erstmal eine Basis, auf der wir aufbauen können. Dazu laden wir uns von der CodeIgniter-Homepage mal die aktuellste Version des Frameworks herunter. Dies ist in diesem Fall Version 3.1.5.

Nun kopieren wir alle Dateien am besten in einen Unterordner „ci-urlshortener“ auf den Webserver (z.B. XAMPP, LAMP, etc.) und rufen dessen Rootverzeichnis im Browser auf, z.B. http://localhost/ci-urlshortener/.

Wenn alle Anforderungen des Frameworks erfüllt sind, sollte hier nun die Willkommensseite von CodeIgniter sichtbar sein.

Damit die Speaking-URLs auch korrekt aufgerufen werden können, legen wir uns eine .htaccess mit folgendem Inhalt an:

RewriteEngine on

RewriteRule ^(assets)($|/) - [L]
RewriteRule .* index.php

Bei Bedarf kann hier auch eine optionale Umgebungsvariable zur Unterscheidung von Entwicklungssytem (“development“), Testsystem (“testing“) oder Produktivsystem (“production“) angegeben werden.

Dies würde dann so aussehen:

SetEnv CI_ENV development

 

In der Autoloader-Konfiguration unter /application/config/autoload.php fügen wir als nächstes den URL- und den Form-Helper sowie die Datenbank-Library hinzu. Wir werden diese Komponenten später brauchen.

$autoload['libraries'] = array('database');
$autoload['helper'] = array('url', 'form');

 

In der Routenkonfiguration unter /application/config/routes.php ändern wir als nächstes den Standard-Controller von „welcome“ auf „home“, da wir später möchten, dass standardmäßig unsere URL-Shortener-Applikation aufgerufen wird.

Zusätzlich definieren wir hier noch zwei weitere Routen: eine für die Redirects (als Catch-All) und eine für die Statistiken.

$route['default_controller'] = 'home';
$route['404_override'] = '';
$route['translate_uri_dashes'] = FALSE;

$route['(:any)/stats'] = 'home/stats/$1';
$route['(:any)'] = 'home/redirect/$1';

Die Redirect-Route (Catch-All) ist bewusst als Letztes definiert, da diese sonst alle anderen Routen abfangen würde und so das Routing nicht ordnungsgemäß funktionieren würde.

 

Index-Seite erstellen, und URLs speichern

Nachdem wir in der Routen-Konfiguration nun auf einen “Home”-Controller verweisen, sollten wir uns diesen auch anlegen. Dazu erstellen wir unter /application/controller eine Home.php (Groß-/Kleinschreibung beachten!).

In dieser legen wir als Nächstes das Grundgerüst an – das wären 3 Actions: eine für die Index-Seite, eine für die eigentliche Weiterleitung (“Redirect”) und eine für die Statistiken.

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Home extends CI_Controller {


	public function __construct()
	{
		parent::__construct();
	}
	
	
	public function index()
	{
		
	}
	
	
	public function redirect( $alias )
	{
		
	}
	
	
	public function stats( $alias )
	{
		
	}
}

Im Constructor oben werden wir später unsere Models laden, sodass wir dies nicht in jeder einzelnen Action separat machen müssen.

Nun haben wir unseren Controller, sowie die notwendigen Actions erstmal definiert. Jetzt legen wir unsere Datenbank-Tabellen wie folgt an:

CREATE TABLE `urls` ( 
	`id` INT NOT NULL AUTO_INCREMENT , 
	`url` VARCHAR(255) NOT NULL , 
	`alias` VARCHAR(100) NOT NULL , 
	`created` DATETIME NOT NULL , 
	PRIMARY KEY (`id`), 
	INDEX (`alias`)
) ENGINE = InnoDB;


CREATE TABLE `statistics` ( 
	`id` INT NOT NULL AUTO_INCREMENT , 
	`url_id` INT NOT NULL , 
	`created` DATETIME NOT NULL , 
	PRIMARY KEY (`id`), 
	INDEX (`url_id`)
) ENGINE = InnoDB;

Wir erhalten dadurch eine Tabelle für die Weiterleitungen (Speicherung von URL mit zugehörigem Alias), sowie eine für Statistiken (Protokollierung von jedem einzelnen Zugriff für eine bestimmte URL).

Legen wir uns nun ein Model für die URL-Tabelle an. Dazu erstellen wir unter /application/models eine neue Datei Url_model.php.

Wir brauchen hier folgende Funktionen: Hinzufügen einer neuen URL (via add_url()) und Auslesen einer URL mittels ID (via get_url_by_id()) bzw. via Alias (via get_url()).

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Url_model extends CI_Model {
	
	
	function add_url( $url )
	{
		// build up data array
		$data = array(
			'url'		=> (string) $url,
			'alias'		=> (string) uniqid(), // creates a random key
			'created'	=> date('Y-m-d H:i:s'),
		);
		 
		// inserts the data into database
		$this->db->insert('urls', $data);
		
		// return this ID of the new inserted record
		return $this->db->insert_id();
	}
	
	
	public function get_url_by_id( $id )
	{
		$this->db->select('*');
		$this->db->from('urls');
		$this->db->where('id', (int) $id);
		
		$result = $this->db->get()->row_object();

		// check if the requested record was found
		if (count($result) > 0) {
			return $result;
		} else {
			return FALSE;
		}
	}
	
	
	public function get_url( $alias )
	{
		$this->db->select('*');
		$this->db->from('urls');
		$this->db->where('alias', (string) $alias);
		
		$result = $this->db->get()->row_object();

		// check if the requested record was found
		if (count($result) > 0) {
			return $result;
		} else {
			return FALSE;
		}
	}
	

}

Die beide Funktionen get_url_by_id() sowie get_url() sind soweit identisch, jedoch mit dem Unterschied, dass wir auf unterschiedliche Datenbank-Felder abfragen.

Nachdem wir nun unser primäres Model für die URLs haben, laden wir dieses wie folgt im Constructor unseres Home-Controllers:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Home extends CI_Controller {


	public function __construct()
	{
		parent::__construct();
		
		// load models
		$this->load->model('Url_model');
	}

Nun können wir auch den nächsten Schritt in Angriff nehmen: Wir erstellen uns unsere Index-Seite.

Dazu definieren wir uns ein Daten-Array mit ein paar Standard-Werten, und definieren eine Logik, was passieren soll, wenn ein POST-Request kommt – in unserem Fall soll hier eine neue URL mit Alias angelegt werden:

	public function index()
	{
		$data = array(
			'error' => false,
			'show_details' => false,
		);
		$post = $this->input->post(NULL, TRUE);

		// check if request method was 'post' - if yes, then try to create short url
		if($post){
			$url = $post['url'];
			
			// validate url
			if (filter_var($url, FILTER_VALIDATE_URL)) {
				
				$id = $this->Url_model->add_url( $url );
				
				$url_data = $this->Url_model->get_url_by_id( $id );
				
				$data['show_details'] = true;
				$data['url_data'] = $url_data;
				
				
			} else {
				$data['error'] = "Invalid URL!";
			}
			
		}
		
		// load view and assign data array
		$this->load->view('home', $data);
	}

In diesem Beispiel-Code findet eine zusätzliche Validierung seitens PHP statt, welche überprüft, ob die vom Benutzer übermittelte URL auch tatsächlich eine gültige URL darstellt.

Wäre dies nicht der Fall, könnte es zu unerwarteten Nebeneffekten bei der eigentlichen Weiterleitung kommen.

Sobald die neue URL in der Datenbank gespeichert wurde, holen wir uns von dort diesen Datensatz und übergeben ihn mit an unsere View – wir wollen dem Benutzer ja auch zeigen, wie sein Alias für die von ihm übermittelte URL aussieht.

Um das machen zu können, fehlt uns nun allerdings noch die zugehörige Home-View (basierend auf dem Bootstrap-Framework) als /application/views/home.php:

<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>CI URL-Shortener</title>

		<!-- Latest compiled and minified CSS -->
		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

		<!-- Custom CSS for basic styling -->
		<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/style.css" />
		
		<!-- Optional CSS for individual theming (powered by Bootswatch - https://bootswatch.com/) -->
		<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/bootstrap_flatly.min.css" />

		<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
		<!--[if lt IE 9]>
		  <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
		  <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
		<![endif]-->
		</head>
	<body>

	<nav class="navbar navbar-default navbar-fixed-top">
		<div class="container">
			<div class="navbar-header">
				<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
					<span class="sr-only">Toggle navigation</span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
				</button>
				<a class="navbar-brand" href="#">CodeIgniter URL-Shortener</a>
			</div>
			<div id="navbar" class="collapse navbar-collapse">
				<ul class="nav navbar-nav">
					<li class="active"><a href="<?php echo base_url(); ?>">Home</a></li>
					<li><a href="#about">About</a></li>
					<li><a href="#contact">Contact</a></li>
				</ul>
			</div>
		</div>
	</nav>

	<div class="container">

		<div class="intro">
			<h1>My awesome URL shortener</h1>
			<p class="lead">
				Lorem ipsum dolor sit amet, consetetur sadipscing elitr, <br/>
				sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, <br/>
				sed diam voluptua.
			</p>
		</div>
		
		<?php echo form_open(); ?>
			<div class="row">
				<div class="col-lg-8 col-lg-offset-2">
					<div class="input-group">
						<input type="url" name="url" class="form-control" placeholder="Enter your URL here..." autocomplete="off" />
						<span class="input-group-btn">
							<button class="btn btn-default" type="submit">Go!</button>
						</span>
					</div>
					
					<?php if( $error ): ?>
					<p class="text-danger"><?php echo $error; ?></p>
					<?php endif; ?>
				</div>
			</div>
		<?php echo form_close(); ?>
		
		<?php if( $show_details ): ?>
		<div class="well url_data">
			<h2>Your new created Short-URL:</h2>
			
			<div class="input-group">
				<input type="text" value="<?php echo base_url( $url_data->alias ); ?>" class="form-control input-lg" readonly="readonly" />
				<span class="input-group-btn">
					<a href="<?php echo base_url( $url_data->alias ); ?>" class="btn btn-default btn-lg" target="_blank">
						<span class="glyphicon glyphicon-share-alt" aria-hidden="true"></span>
					</a>
				</span>
			</div>
			
			<p>&nbsp;</p>
			<p>You can view statistics for this URL here: <a href="<?php echo base_url( $url_data->alias .'/stats' ); ?>" target="_blank"><?php echo base_url( $url_data->alias .'/stats' ); ?></a></p>
			
		</div>
		<?php endif; ?>

	</div>


	<!-- Bootstrap core JavaScript
	================================================== -->
	<!-- Placed at the end of the document so the pages load faster -->
	<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
	<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
	</body>
	</html>

Beim verlinkten bootstrap_flatly.min.css handelt es sich um das kostenlose Bootstrap-Theme “Flatly” von Bootswatch.

Dieses CSS-Theme ist optional und kann daher also gerne auch weggelassen werden.

In der style.css definieren wir uns lediglich ein paar Basis-Styles:

body {
	padding-top: 50px;
}
.intro {
	padding: 40px 15px;
	text-align: center;
}

.intro h1 {
	margin-bottom: 40px;
}

.url_data {
	margin-top: 60px;
	text-align: center;
}

.url_data h2 {
	font-size: 25px;
}

Die Home-View selbst stellt lediglich ein Formular zum Absenden einer beliebigen URL sowie unterhalb eine Bereich zum Anzeigen des generierten Alias an.

Nun können wir zum ersten Mal unsere Anwendung testen, indem wir eine beliebige URL ins Formular eingeben, und dieses dann absenden.

In der Datenbank sollten wir nun diese inkl. einem Alias vorfinden, welcher uns auf der Index-Seite unterhalb des Formulars angezeigt werden sollte.

 

Weiterleitung konfigurieren

Da wir anstatt einfach nur auf die Ziel-URL weiterzuleiten, die Anfragen an unser System auch auswerten möchten, legen wir uns nun ein Model für die Statistik-Tabelle an. Dazu erstellen wir unter /application/models eine neue Datei Statistics_model.php mit folgendem Inhalt:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Statistics_model extends CI_Model {
	
	
	function add_log( $url_id )
	{
		// build up data array
		$data = array(
			'url_id'		=> (int) $url_id,
			'created'	=> date('Y-m-d H:i:s'),
		);
		 
		// inserts the data into database
		$this->db->insert('statistics', $data);
		
		// return this ID of the new inserted record
		return $this->db->insert_id();
	}
	
	
	public function get_logs( $url_id )
	{
		$this->db->select( array('*', 'COUNT(id) AS sum') );
		$this->db->from('statistics');
		$this->db->where('url_id', (int) $url_id);
		$this->db->group_by('DATE_FORMAT(created, "%m-%y-%d")');
		$this->db->order_by('YEAR(created) ASC, MONTH(created) ASC, DAY(created) ASC');
		
		$result = $this->db->get()->result_object();

		// check if the requested record was found
		if (count($result) > 0) {
			return $result;
		} else {
			return FALSE;
		}
	}
	

}

Die Funktion hier beschränkt sich auf das Anlegen eines neuen Eintrags (via add_log()) für eine bestimmte URL, sowie dem Auslesen aller Einträge zu einer URL (via get_logs()).

Bei Letzterem wird nach Datum gruppiert, und zusätzlich die Gesamtzahl der Zugriffe pro Tag als sum zurückgegeben.

Dieses Model laden wir anschließend gleich wie das URL-Model im Constructor des Home-Controllers:

<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Home extends CI_Controller {


	public function __construct()
	{
		parent::__construct();
		
		// load models
		$this->load->model('Url_model');
		$this->load->model('Statistics_model');
	}

 

Nachdem wir vorhin bereits eine leere “redirect“-Action definiert haben, wird es Zeit, dieser auch eine Funktion zu geben.

Der als $alias übergebene Parameter ist jener, welcher zuvor bei der Speicherung der URL generiert wurde.

Mit dessen Hilfe suchen wir nun mittels der zuvor erstellten get_url()-Funktion in der Datenbank nach dem gewünschten Eintrag.

	public function redirect( $alias )
	{
		$url_data = $this->Url_model->get_url( $alias );
		
		// check if there's an url with this alias
		if(!$url_data){
		
			header("HTTP/1.0 404 Not Found");
			$this->load->view('not_found');
		
		}else{
			
			$this->Statistics_model->add_log( $url_data->id );
			
			header('Location: ' . $url_data->url, true, 302);
			exit();
		}
		
	}

Wenn der entsprechende Datenbank-Eintrag gefunden wurde, können wir die eigentliche Weiterleitung mittels des Location-Headers durchführen.

(Die “302” am Ende gibt den HTTP-Status-Code an, welcher mitgesendet werden soll. In der Regel wäre dies allerdings eine “301” (auf Produktiv-System aus SEO-Sicht zu bevorzugen) – nachdem aber eine Weiterleitung mit einem 301-Header von den Browser gecacht wird, wäre es zum Testen äußert unpraktisch – daher in diesem Fall “302”.)

Vor diesem Redirect erfassen wir aber noch den Zugriff auf die URL über unser Statistics-Model und speichern diesen.

Wichtig ist, dass nach dem Location-Header (oder generell nach einer Header-Ausgabe) keine weitere Ausgabe erfolgt – daher vorsichtshalber noch ein exit() danach.

 

Für den Fall, dass wir zu einem Alias keinen Datenbank-Eintrag finden können, verweisen wir einfach auf eine “Not found”-View:

<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>CI URL-Shortener</title>

		<!-- Latest compiled and minified CSS -->
		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

		<!-- Custom CSS for basic styling -->
		<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/style.css" />
		
		<!-- Optional CSS for individual theming (powered by Bootswatch - https://bootswatch.com/) -->
		<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/bootstrap_flatly.min.css" />

		<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
		<!--[if lt IE 9]>
		  <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
		  <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
		<![endif]-->
		</head>
	<body>

	<nav class="navbar navbar-default navbar-fixed-top">
		<div class="container">
			<div class="navbar-header">
				<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
					<span class="sr-only">Toggle navigation</span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
				</button>
				<a class="navbar-brand" href="#">CodeIgniter URL-Shortener</a>
			</div>
			<div id="navbar" class="collapse navbar-collapse">
				<ul class="nav navbar-nav">
					<li><a href="<?php echo base_url(); ?>">Home</a></li>
					<li><a href="#about">About</a></li>
					<li><a href="#contact">Contact</a></li>
				</ul>
			</div>
		</div>
	</nav>

	<div class="container">

		<div class="intro">
			<h1>Whoops! Something went wrong...</h1>
			<p class="lead">
				Lorem ipsum dolor sit amet, consetetur sadipscing elitr, <br/>
				sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, <br/>
				sed diam voluptua.
			</p>
		</div>
		

	</div>


	<!-- Bootstrap core JavaScript
	================================================== -->
	<!-- Placed at the end of the document so the pages load faster -->
	<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
	<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
	</body>
	</html>

Diese View wird als /application/views/not_found.php gespeichert. Sie kann individuell gestaltet werden. In unserem Fall hat sie keine besondere Funktion.

Ab nun kann die beim letzten Test zuvor generierte Kurz-URL getestet werden – wenn wir alles richtig gemacht haben, wird man nun auf die Ziel-URL weitergeleitet und ein Log-Eintrag über den Zugriff in der Statistik-Tabelle gespeichert.

 

Statistiken anzeigen

Die “stats“-Action wird auf den ersten Blick der “redirect“-Action ähneln. Das liegt daran liegt, dass auch die Funktion ziemlich gleich ist: bei beiden wird anhand eines übergebenen Alias eine Suche in der Datenbank durchgeführt.

Bei der “redirect“-Action erfolgt eine Weiterleitung, und bei der “stats“-Action fragen wir anhand dieser Daten alle vorhanden Log-Einträge zu dieser URL ab.

	public function stats( $alias )
	{
		$url_data = $this->Url_model->get_url( $alias );
		
		// check if there's an url with this alias
		if(!$url_data){

			header("HTTP/1.0 404 Not Found");
			$this->load->view('not_found');

		}else{
			
			$logs = $this->Statistics_model->get_logs( $url_data->id );
			
			$data = array(
				'url_data'	=> $url_data,
				'logs'		=> $logs,
			);
			
			$this->load->view('stats', $data);

		}
		
	}

Auch hier kommt für den Fall, dass es keinen Datenbank-Eintrag für einen bestimmten Alias gibt, unsere “Not found”-View von oben zum Einsatz.

Die zugehörige View (/application/views/stats.php) würde dann folgendermaßen aussehen:

<?php defined('BASEPATH') OR exit('No direct script access allowed'); ?>
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<meta http-equiv="X-UA-Compatible" content="IE=edge">
		<meta name="viewport" content="width=device-width, initial-scale=1">
		<meta name="description" content="">
		<meta name="author" content="">

		<title>CI URL-Shortener</title>

		<!-- Latest compiled and minified CSS -->
		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

		<!-- Custom CSS for basic styling -->
		<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/style.css" />
		
		<!-- Optional CSS for individual theming (powered by Bootswatch - https://bootswatch.com/) -->
		<link rel="stylesheet" href="<?php echo base_url(); ?>assets/css/bootstrap_flatly.min.css" />

		<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
		<!--[if lt IE 9]>
		  <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
		  <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
		<![endif]-->
		</head>
	<body>

	<nav class="navbar navbar-default navbar-fixed-top">
		<div class="container">
			<div class="navbar-header">
				<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
					<span class="sr-only">Toggle navigation</span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
					<span class="icon-bar"></span>
				</button>
				<a class="navbar-brand" href="#">CodeIgniter URL-Shortener</a>
			</div>
			<div id="navbar" class="collapse navbar-collapse">
				<ul class="nav navbar-nav">
					<li><a href="<?php echo base_url(); ?>">Home</a></li>
					<li><a href="#about">About</a></li>
					<li><a href="#contact">Contact</a></li>
				</ul>
			</div>
		</div>
	</nav>

	<div class="container">

		<div class="intro">
			<h1>Statistics for URL</h1>
			<p class="lead">
				Lorem ipsum dolor sit amet, consetetur sadipscing elitr, <br/>
				sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, <br/>
				sed diam voluptua.
			</p>
		</div>
		
		<div class="well">		
			<h3><?php echo $url_data->url; ?></h3>
			<p class="help-block">Created: <?php echo $url_data->created; ?></p>
		</div>

		
		<?php if($logs): ?>
		
		<table class="table table-striped">
			<thead>
				<tr>
					<th>Date</th>
					<th>Number of clicks</th>
				</tr>
			</thead>
			<tbody>
				<?php foreach($logs as $log): ?>
				<tr>
					<th><?php echo date('Y-m-d', strtotime( $log->created )); ?></th>
					<th><?php echo $log->sum; ?></th>
				</tr>
				<?php endforeach; ?>
			</tbody>
		</table>
		
		<?php else: ?>
		
		<div class="alert alert-warning">
			<p>Unfortunately, there're no statistics available.
		</div>
		
		<?php endif; ?>
				

	</div>


	<!-- Bootstrap core JavaScript
	================================================== -->
	<!-- Placed at the end of the document so the pages load faster -->
	<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js" integrity="sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4=" crossorigin="anonymous"></script>
	<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
	</body>
	</html>

Wir iterieren in einer foreach-Schleife über alle vorhanden Log-Einträge drüber und zeigen diese in Form einer Tabelle an.

Wer möchte, kann diese Daten auch gerne in Form eines Diagramms anzeigen. Im Internet finden sich hierfür jedenfalls zahlreiche jQuery-Plugins.

Für den Fall, dass es keine Log-Einträge gibt (wenn z.B. die Kurz-URL noch nie aufgerufen wurde), folgt ein entsprechender Hinweis-Text.

 

Fazit

Kurz-URL-Dienste sind vom Aufbau her trivial. Ihr Einsatz ist allerdings nicht immer so einfach: Sie können leicht missbraucht werden, da für den End-Nutzer nicht ersichtlich ist, wohin er weitergeleitet werden wird.

Zwar haben die großen Anbieter teilweise bereits ziemlich gute Malware- und Phishing-Schutzfunktionen implementiert, aber dennoch bleibt die Sicherheit trotz des relativ hohen Sicherheitslevels umstritten.

Die mögliche Tracking-Option (in diesem Tutorial auf das Logging von Zugriffen beschränkt) kann auch beliebig erweitert werden – z.B. um die Erfassung der IP des Benutzers (sofern möglich, wäre es aus Datenschutzgründen empfehlenswert die letzten Teile der IP nicht bzw. nur anonymisiert zu speichern), und daraus resultierend dessen Land (via GeoIP) oder auch eventuelle Referrers, etc.

Die Auswertung solcher Daten ist gerade im Marketing-Bereich wichtig, da man auf diese Art Werbekampagnen (etc.) gezielter und auch kosteneffizienter schalten kann.

Aus diesem Grund sind solche Kurz-URL-Dienste besonders für Unternehmen interessant. Solche Short-URLs werden beispielsweise in Newslettern verwendet, um so herauszufinden, wie oft ein Link angeklickt wurde. ;)