We shorten links without fluff (F3)

We shorten links without fluff (F3)

It's not shameful to shorten links for 13 years, right? A beginner, and not just a beginner, should try writing their own Link Tamer while exploring some new framework. That's what I've been up to. What can I say — the fifth bootstrap, a lean framework, and a piece of soul.

Here demo, and here code. For readers like me 😉

Framework, right?

Of course not Laravel and similar ones — today we'll manage with 65 kilobytes. FatFreeFramework. If you're familiar with Python Flask, you'll get the feeling that you've seen this somewhere before:

#роутинг во Фласке
@app.route('/')
def hello_world():
    return 'Hello, World!'
//роутинг в Обезжиренном
$f3->route('GET /',
    function() {
        echo 'Hello, world!';
    }
);

Alright, forget it. Let's download .zip from the official site, unpack it into a folder that immediately opens in your Favorite Code Editor. Clear index.php and delete everything from /ui.

It's all very straightforward — in the folder ui we have all the Views, or simply put — enhanced HTML templates that we will show users when they visit a specific URL.

Here's the skeleton of our "application":

set('DEBUG', 1);
if ((float)PCRE_VERSIONconfig('config.ini');

// ALL OTHER CODE WILL BE WRITTEN HERE

$f3->run();

That's all you need to know to get started. Let's begin coding!

[For development, I used a local XAMPP on Windows and VS Code, this article was written in Notion.]

Homepage

Let's start with the homepage. Makes sense, doesn't it?

//Файл: index.php

$f3->route('GET /',
    function($f3) { //чтобы использовать функции F3 передаем его в роут
                $view = new View; // создаем вьюшку
        echo $view->render('home.htm'); //рендерим шаблон
    }
);

Now we need to write that template. For simplicity, I used bootstrap v5 alpha.

Don't forget to create all templates in the folder ui, otherwise they won't be visible to the framework.

<!-- Файл: ui/home.htm -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="<?php echo $ENCODING; ?>" />
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>We write (code), we shorten (links)!</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
    </head>
    <body class="text-center bg-dark text-light"> <!-- темная тема ;) -->

        <!-- менюшка -->
        <nav class="m-2">
            <ul class="nav nav-pills justify-content-center">
                <li class="nav-item">
                    <a class="nav-link active" aria-current="page" href="#">Home</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Article on Habr</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="https://nikonovs.ru">Creator</a>
                </li>
            </ul>
        </nav>

        <div class="container">
        <h1>Short links are here.</h1>

        <!-- Будем отправлять данные POST-запросом на /newLink -->
        <form class="mt-5 mb-3" action="/en/newLink/" method="POST" data-trp-original-action="/newLink/">
            <div class="row justify-content-center">
                <div class="col-auto">
                <label for="inputLink" class="col-form-label">Enter the link:</label>
                </div>
                <div class="col-auto">
                <input required placeholder="https://" type="url" name="link" id="inputLink" class="form-control mb-1" aria-describedby="inputLink">
                </div>
                <div class="col-auto">
                <button type="submit" class="btn btn-outline-primary">Shorten!</button>
                </div>
            </div>
        <input type="hidden" name="trp-form-language" value="en"/></form>

        <!-- немного -->
        <p class="text-left m-auto mb-5" style="max-width: 30rem;">Lorem ipsum dolor sit, amet consectetur adipisicing elit. Omnis illum molestiae hic fugiat molestias nemo, architecto beatae repellat ullam exercitationem non ab, necessitatibus maxime quod iure ipsa quam quos! Reprehenderit. Lorem ipsum dolor, sit amet consectetur adipisicing elit. Necessitatibus eos sapiente voluptates veniam sequi delectus totam tenetur praesentium obcaecati. Repudiandae quisquam, ipsa ullam corrupti molestiae minima optio nihil est modi?</p>

        <footer class="m-2">Made with <img width="20" height="20" src="https://image.flaticon.com/icons/svg/833/833472.svg" alt="with love">, <a href="https://v5.getbootstrap.com/">Bootstrap 5</a>    and <a href="https://fatfreeframework.com/">without fat</a></footer>
        </div>
    </body>
</html>

That's it, our homepage is already working. The form sends a POST request with the link that needs to be shortened.
Now the most interesting part (not really).

Working with the Database

Let's create a database — MySQL. If you have PhpMyAdmin installed, create a new database named "linker", and then execute this SQL:

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";

CREATE TABLE IF NOT EXISTS `links` (
  `code` varchar(4) NOT NULL,
  `link` varchar(1000) NOT NULL,
  `hits` int(255) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

ALTER TABLE `links`
  ADD UNIQUE KEY `code` (`code`);

We will have 3 fields for each link:

  1. Code — these are random 4 characters after the domain that will handle the redirection, like example.com/ABC1
  2. Link — The unshortened link.
  3. Hits — the number of clicks on the shortened link.

I'll briefly explain the principle of working with the database, without any fluff.

set('result', db->exec('SELECT * FROM wherever')); 
// they will be available in templates as 

// Alternatively, you can use the built-in SQL Mapper:
row = new DBSQLMapper(db, 'links');

row->load(array('link="https://habrahabr.ru"')); // now all columns of the row where the link to Habr is available:
row_value = row->somerow; // like this

// Of course, you can modify the values:
row->link = 'https://habr.com';
row->save(); // changes need to be saved, what did you think?

// more information on working with DB is available here: https://a.nikonovs.ru/MPHR I strongly recommend reading it, at least with the help of the translator built into the browser.
?>

Let's start shortening.

Processing new link

Creating a new View in index, which will handle the request from the form on the main page.

First, let's create a new template that is very similar to the first one (home.htm) — "newLink.htm".
There we will display the shortened link and the number of clicks on it (to see this "statistics" again, you need to shorten the same link again — the address will remain the same).
To output, we will use the trick with "passing variables":

set('link', $shorted_link);
view = new View;
echo view->render('newLink.htm');
//now in the template you can use:

And here is the listing newLink.html:

<!-- Файл: newLink.htm -->

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="<?php echo $ENCODING; ?>" />
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>We write (code), we shorten (links)!</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous">
    </head>
    <body class="text-center bg-dark text-light">
        <nav class="m-2">
            <ul class="nav nav-pills justify-content-center">
                <li class="nav-item">
                    <a class="nav-link" aria-current="page" href="/en/">Home</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Article on Habr</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="https://nikonovs.ru">Creator</a>
                </li>
            </ul>
        </nav>

        <div class="container">
        <h1>Short links are here.</h1>

        <!-- Убираем из формы функционал формы и выводим переменные -->
        <form class="mt-5 mb-3" action="">
            <div class="row justify-content-center">
                <div class="col-auto">
                    <label for="inputLink" class="col-form-label">Shortened:</label>
                </div>
                <div class="col-auto">
                    <input disabled required type="url" name="link" id="inputLink" class="form-control disabled" aria-describedby="inputLink" value="<?= $link ?>">
                </div>
            </div>
            <p class="m-2 text-secondary">Clicked on this link: ``</p>
        <input type="hidden" name="trp-form-language" value="en"/></form>

        <a href="/en/" class="mt-3 mb-5 btn btn-primary btn-lg">RETURN TO HOME</a>

        <footer class="m-2">Made with <img width="20" height="20" src="https://image.flaticon.com/icons/svg/833/833472.svg" alt="with love">, <a href="https://v5.getbootstrap.com/">Bootstrap 5</a>    and <a href="https://fatfreeframework.com/">without fat</a></footer>
        </div>
    </body>
</html>

Let's write the route.

$f3->route('GET|POST /newLink', // We will handle both POST and GET
    function($f3) {

            $db = new DBSQL( // Connection to the database for each route
                'mysql:host=localhost;port=3306;dbname=linker',
                'root',
                ''
            );

            // A great function to generate random characters:
            $permitted_chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
            function generate_string($input, $strength = 4) {
                $input_length = strlen($input);
                $random_string = '';
                for($i = 0; $i load(array('link="'. $link .'"'));
            if ($check->dry()) {
                $g_code = generate_string($permitted_chars);
                $row = new DBSQLMapper($db,'links');
                $row->reset();
                $row->code = $g_code;
                $row->link = $link;
                $row->save();
            } else {
                $g_code = $check->code; // If link is repeated, show the old code
            }

            $short_link = 'https://'. $_SERVER['HTTP_HOST'] . '/' . $g_code; // Construct the final link

            // Parameters from $_POST can be obtained using $f3->get('POST'), dot notation is supported (correct me if I'm wrong): the parameter "link" can be obtained like this:
            $link = $f3->get('POST.link');

            if ( !empty($f3->get('POST')) ) { // Output HTML only if POST is not empty.

            $f3->set('link', $short_link);
            $f3->set('hits', $check->hits);
            $view = new View;
            echo $view->render('newLink.htm');

            } else { // Otherwise - redirect to the homepage
                $f3->$f3->reroute('/');
            }

        }
);

Done! In fact, that was easy.

Redirecting

Just one last step:

  1. Get the parameter from the URL
  2. Check its existence in the database
  3. Retrieve the corresponding link from the database
  4. Redirect the user
  5. Profit!

Let's continue writing code after the last route.

$f3->route('GET /@code', // specify the parameter after "@", it will go to PARAMS
    function($f3) {

        // again define $db
        $db = new DBSQL(
            'mysql:host=localhost;port=3306;dbname=linker',
            'root',
            ''
        );

        $code = $f3->get('PARAMS.code'); // get the parameter

        $link = new DBSQLMapper($db,'links'); 

        // if we can get the link from the DB - increase the hits and redirect
        if ($link->load(array('code="'.$code.'"', 'link=?'))) {
            $link->hits++;
            $link->save();

            $f3->reroute($link->link);
        } else {
            $f3->reroute('/'); // if no such link exists - welcome to the homepage
        }
    }
);

You may have noticed that in the route newLink, and in the route above, they will be the same — after all, code it may coincide with "newLink" (it cannot, only uppercase letters are allowed in the generator), but since it is defined first, it will be executed first.

$f3→run()!

Thank you for reading!
I would be happy if you left a comment and corrected me if something is wrong.

And as a homework assignment or proof of the author's laziness (me), I leave a little list of what can be done. After all, it's better to learn from practice!

  • It is certainly unlikely, but during generation $g_code it might repeat, so I suggest you write a function that checks for this.
  • You can also create proper statistics and display it when redirecting to /@code/stats
  • Prohibit creating links to the shortening service itself, create a list of "protected" resources from being shortened
  • I strongly recommend even in such a small task to validate input on the server side, with appropriate error messages; do not rely solely on adding the required attribute and type="url" to the input field.
    RedComrade

  • Suggest in the comments…

    In touch)

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster