IncludeBeer

How to build a basic web application with CodeIgniter 4 (part 1)

Published on 22 November 2020
Article's image
Image credit: Bram Naus

Creating a web application with CodeIgniter 4 is really easy. In this series of articles I put the emphasis on the PHP code and not on the installation of CI, nor on the look of the web pages. If you haven't already, start by installing CodeIgniter. You can install it on a test server or on your personal computer. In the examples in this article, I'm using MAMP (Apache, MySQL, and PHP on macOS) with a virtual server http://ci4.test:8888 that points to the directory /Applications/MAMP/htdocs/ci4.test/public.

First, if your website is not in english, you can install the translation files for your language in the directory /Applications/MAMP/htdocs/ci4.test/app/Language/. I configured the minimum for this application and deleted the sample files:

app/Config/App.php

// Set the web site's URL
public $baseURL = 'http://ci4.test:8888';

// Set the index page as blank to avoid having index.php in the URL
public $indexPage = '';

// Set the default language
public $defaultLocale = 'en';

// Set the languages supported by the application
public $supportedLocales = ['en'];

// Set the timezone
public $appTimezone = 'America/Toronto';

app/Config/Logger.php

// Set the logging threshold to the max to help when debugging
public $threshold = 9;

app/Config/Routes.php

// Set the default controller
$routes->setDefaultController('RecipesController');

// Disable AutoRoute
$routes->setAutoRoute(false);

// Define the route for '/'
$routes->get('/', 'RecipesController::index');

Delete the "welcome" page

cd /Applications/MAMP/htdocs/ci4.test
rm app/Controllers/Home.php
rm app/Views/welcome_message.php

Model, view and controller

Just like CodeIgniter 3, version 4 still use the MVC pattern (Model-View-Controller). The controller being the entry point for HTTP requests. The model being the data source (usually a MySQL database). And the view being the final HTML document returned to the browser. So for this very simple example of a recipe site, here's what each of those files looks like. At this point, we haven't defined a database yet, so we don't have a file for the model. We only have a controller and a view to display the dummy data:

app/Controllers/RecipesController.php

<?php namespace App\Controllers;

class RecipesController extends BaseController
{
    public function index()
    {
        // Collect all the data used by the view in a $data array
        $data = [
            'page_title' => "My Recipes",
            'page_subtitle' => "I present you my favorite recipes...",
            'recipes' => $this->_dummy_data(),
        ];

        /* Each of the items in the $data array will be accessible
         * in the view by variables with the same name as the key:
         * $page_title, $page_subtitle and $recipes
         */
        return view('recipe_list', $data);
    }

    /**
     * Dummy data because we don't have a model and a database yet.
     */
    private function _dummy_data ()
    {
        return [
            [
                'title' => "Boiling Water",
                'ingredients' => ["Fresh water"],
                'instructions' => "Put the water in a cauldron and boil."
            ],
            [
                'title' => "Tea",
                'ingredients' => ["Fresh water", "Tea bag"],
                'instructions' => "Prepare the recipe for boiling water. Put the water in a cup, add the tea bag and let it steep for a few minutes."
            ],
            [
                'title' => "Glass of water",
                'ingredients' => ["Fresh water", "Ice cubes", "Lemon slice (optional)"],
                'instructions' => "Put ice cubes in a tall glass and fill with water. Add a slice of lemon if desired."
            ],
        ];
    }
}

app/Views/recipe_list.php

<?php
/**
 * @var string $page_title     The page title (automatically created by CI from the $data array)
 * @var string $page_subtitle  The page subtitle (automatically created by CI from the $data array)
 * @var array  $recipes        List of recipes (automatically created by CI from the $data array)
 * @var array  $recipe         One recipe (created by the foreach instruction)
 * @var string $ingredient     One ingredient (created by the foreach instruction)
 */
?>
<!DOCTYPE html>
<html lang="en">
<head>
<title><?= esc($page_title) ?></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css"
      integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk"
      crossorigin="anonymous">

<style type="text/css">
.title
{
    padding: 3rem 1.5rem;
}

article
{
    padding: 1.5rem 1.5rem;
}
</style>

</head>

<body>

<main role="main" class="container">
    <div class="title">
        <h1>
            <?= esc($page_title) ?>
            <small class="text-muted"><?= esc($page_subtitle) ?></small>
        </h1>
    </div>

    <div class="container">

<?php foreach ($recipes as $recipe): ?>
        <article>
            <h3><?= esc($recipe['title']) ?></h3>
            <h5>Ingredients</h5>
            <ul>
            <?php foreach ($recipe['ingredients'] as $ingredient): ?>
                <li><?= esc($ingredient) ?></li>
            <?php endforeach; ?>
            </ul>
            <h5>Instructions</h5>
            <p><?= esc($recipe['instructions']) ?></p>
        </article>
        <hr>
<?php endforeach; ?>

    </div>

</main>

<footer>
    <p class="text-center">&copy; 2020 My recipe website</p>
</footer>

</body>
</html>

First part finished!

You can now browse to the page http://ci4.test:8888 which will display all three recipes, including my famous boiling water recipe! Obviously this is just a very simplistic example. In a real application the recipes should be in a database. This is what we will do right now in the second part.