Codementor Events

Mr Simon Cohen on Slim

Published Jun 06, 2018Last updated Dec 03, 2018
Mr Simon Cohen on Slim

Introduction

Slim is a micro framework, it can help you to build a web application fast, not only a working website, but also a prototype if you need a fast prototyping tool.

Over a year ago I just built a working website in a few days (of course it didn't include the frontend development - HTML, CSS & JavaScript) for Mr Simon Cohen with Slim entirely for the site's soft launch. Simon is a keynote speaker and broadcaster who gave away a million pound company, Global Tolerance, for his family and currently living in Cornwall, United Kingdom. He is happy for me to publish the code (usually you should not simply publish the source code of your client's website) for learning and sharing purposes. Simon's current site is on WordPress which was developed with a custom theme and launched in a couple of months after the soft launch.

As said, Slim is a micro framework, so it focuses on HTTP request and response. It does not provide a CMS like WordPress or Drupal does. Of course you can develop a full CMS with Slim if you are up for it.

Concept

Simon's Slim website uses a very simple application structure for fast development. If you want a more complicated and modular structure, you can take a look on this post that I made sometime ago - Creating a Modular Slim Application.

The structure for Simon's:

/path/to/project/
    bootstrap.php
    config/
    library/
    module/
    source/
    vendor/
    templates/
    public/
      .htaccess
      index.php

Just as many other Slim applications, this is how you run the Slim's instance:

// Import classes.
use Slim\App as Slim;

// Include application bootstrap.
chdir(dirname(__DIR__));
require 'bootstrap.php';

// Get the application settings file.
$settings = require 'config/application.php';

// Get an instance of Slim.
$app = new Slim(["settings" => $settings]);

// Set up dependencies.
require 'config/dependencies.php';

// Register routes.
require 'config/routes.php';

// Run the application!
$app->run();

Template Engine

Twig was chosen for Simon's, so you would install it this way:

composer require slim/twig-view

And hook it up to Slim's container in config/dependencies.php:

$container = $app->getContainer();
$container['view'] = function ($container) {
    $loader = new Twig_Loader_Filesystem('templates/');

    // Add additional paths to the existing.
    $loader->addPath('templates/layout/');

    $twig = new Twig_Environment($loader, array(
        // options
    ));

    // Add twig extension.
    $twig->addExtension(new \Twig_Extensions_Extension_Array());
    return $twig;
};

Then this is how you would call it in one of your routes:

// Home route.

// PSR 7 standard.
use Slim\Http\Request;
use Slim\Http\Response;
$app->get('/', function (Request $request, Response $response, array $args) {
    // Get an instance of the Twig Environment.
    $twig = $this->view;

    // From that get the Twig Loader instance (file loader in this case).
    $loader = $twig->getLoader();

    // Add the module template and additional paths to the existing.
    $loader->addPath('templates/home/');
    $loader->addPath('templates/keywords/');

    // Render.
    $response->getBody()->write(
        $twig->render('index.html', ['current' => ''])
    );

    return $response;
});

You can follow the guide from Twig or Slim on how to use Twig in more details. It is as simple as:

{% extends "layout.html" %}

{% block body %}
<h1>User List</h1>
<ul>
    <li><a href="{{ path_for('profile', { 'name': 'josh' }) }}">Josh</a></li>
</ul>
{% endblock %}

Routes and Code Logic

In a more complex application, you should separate the HTTP route and the logic of your code, just as in the module structure that I proposed. But Simon's was not intended to scale, nor was for the CMS development, so I kept the logic in the route for its simplicity. For example, in Contact page's routes, it has:

$app->post('/contact', function (Request $request, Response $response) {

    $data = $request->getParsedBody();

    // https://bootstrapbay.com/blog/working-bootstrap-contact-form/
    if (isset($data["submit"])) {
        $name = $data['name'];
        $email = $data['email'];
        $message = $data['message'];
        $human = $data['g-recaptcha-response'];

        $errName = null;
        $errEmail = null;
        $errMessage = null;
        $errHuman = null;
        $ok = null;
        $fail = null;

        // Check if name has been entered
        if (!$name) {
            $errName = 'Please enter your name';
        }

        // Check if email has been entered and is valid
        if (!$email || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errEmail = 'Please enter a valid email address';
        }

        //Check if message has been entered
        if (!$message) {
            $errMessage = 'Please enter your message';
        }

        //Check if simple anti-bot test is correct
        if (!$human) {
            $errHuman = 'You must complete the captcha to submit.';
        } else {
            // Google recaptcha integration.

            // Get your own secret key from https://developers.google.com/recaptcha/intro
            $secret = "xxxxxx";

            $gRecaptchaResponse = $human;
            $remoteIp = $_SERVER["REMOTE_ADDR"];

            $recaptcha = new \ReCaptcha\ReCaptcha($secret);
            $resp = $recaptcha->verify($gRecaptchaResponse, $remoteIp);
            if ($resp->isSuccess()) {
                // verified!
            } else {
                // error!
                $errHuman = 'Your captcha verification failed.';
            }
        }

        // Mailer variables.

        // Receiver's email.
        $to = 'simon@mrsimoncohen.com';

        $subject = "Message from Contact | Mr Simon Cohen ";

        // Always set content-type when sending HTML email
        $headers = "MIME-Version: 1.0" . "\r\n";
        $headers .= "Content-Type: text/plain; charset=utf-8\r\n";
        $headers .= "X-Priority: 1\r\n";
        $headers .= "From: ". $name . " <". $email . ">\r\n";
        $headers .= "Reply-To: " . $email . "\r\n";

        // If there are no errors, send the email
        if (!$errName && !$errEmail && !$errMessage && !$errHuman) {
            if (mail($to, $subject, strip_tags($message), $headers)) {
                $ok = 'Thank you for leaving your mark. I will be in touch soon.';
            } else {
                $fail = 'Sorry there was an error sending your message. Please try again later.';
            }
        }
    }

    // Get an instance of the Twig Environment.
    $twig = $this->view;
    ...
    ...
}

But in a complex application, you may want to have a controller, model, gateway and so on to handle the logic above, for example:

-------------
_routes/
    insert_user.php
    update_user.php
    fetch_users.php
    fetch_user.php
    delete_user.php

Controller/
    Controller.php <-- abstract class to be extended by the following classes:
    InsertController.php
    UpdateController.php
    FetchController.php
    DeleteController.php

Gateway/
    Gateway.php <-- abstract class to be extended by the following classes:
    InsertGateway.php
    UpdateGateway.php
    FetchGateway.php
    DeleteGateway.php

Mapper/
    Mapper.php <-- abstract class to be extended by the following classes:
    InsertMapper.php
    UpdateMapper.php
    FetchMapper.php
    DeleteMapper.php

Model/
    Collection.php
    Model.php

index.php <-- the user main route.
-------------

And in the route PHP file, you just have to do so:

// PSR 7 standard.
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

$app->put('/users', function (Request $request, Response $response, $args) {

    // Autowiring the controller.
    $controller = $this->get('Monsoon\User\Controller\UpdateController');

    // Obtain result.
    $result = $controller->updateUser($request);
    $response->getBody()->write(json_encode($result));
});

Conclusion

That's it. Hope you can see how easy it is to build a working application or a website with Slim when you come up against a deadline or a launch date. You can download this working sample on GitHub and run it on your local machine:

# Dependencies
$ cd [my-app-name]
$ composer update

# Production build
$ php -S 0.0.0.0:8080 -t public

Make sure that the port 8080 is not occupied by any other app before accessing it at http://0.0.0.0:8080/. You should see the following screen shots on your browser:

(Desktop view)

preview-desktop.png

(Mobile view)

preview-mobile.png

Have fun!

Notes

Discover and read more posts from LAU TIAM KOK
get started