Go to homepage

Establishing a base for the future

25th January 2021 | 8 minutes

TL;DR: This post describes the structure behind my recently published pretzelhands/base repository.

For any seasoned PHP developer, looking at the repository should be enough. If you're interested in the rationale, feel free to read along.

This base project is intended as the common ground for any future projects built as part of this blog. For small projects it can feel over the top to bring along a whole Laravel install when a much smaller framework would do.

It also ensures that no one needs to be deep into any one ecosystem, unless it is explicitly mentioned.

Anybody who has recently read my Telegram bot post will already recognize the project structure.

Requirements

  • PHP 7.4, ideally PHP 8.0
  • Composer

We explicitly do not need a local web server such as Apache or nginx. We'll use a special command to get a shareable link for our project.

Creating the project structure

As any modern PHP project should, the app uses Composer to define and install dependencies. So let's set that up.

$ composer init

And for the basic setup we only require one other dependency. That's vlucas/phpdotenv. We use it to load sensitive data from a .env file. Let's install it!

$ composer require vlucas/phpdotenv

Then I want to create a folder structure that looks something like this

base-project/
├─ vendor/
├─ composer.json
├─ composer.lock
├─ .env
├─ bootstrap.php
├─ src/
│  ├─ Pretzel/
│  ├─ helpers.php
├─ public/
│  ├─ index.php

In this structure we first have the files that Composer generates. The vendor/ folder for our dependencies and autoloader, the composer.json defining our project meta data and dependencies and the composer.lock file pinning our dependencies to specific versions.

.env holds our sensitive data that can change between your local computer and your actual server. An example would be an API key to access GitHub or a key that the application uses for encryption.

bootstrap.php pulls in the Composer autoloader and sets up the loading of the .env files. It's used for any setup tasks that always need to run. We will usually include it in the PHP files located in public/

src/Pretzel contains our application specific code. Anything that makes your idea come to live! The folder name is based on the namespace. This will defined later and you can change it to anything you like.

src/helpers.php contains small helper functions that we want to use every now and again. For example, we'll define a convenience function for getting values from our .env file.

public/ contains all the things we want to expose to the scary internet. In MVC-speak these are our controllers, but in a very low-tech way. Here we deal with user input and maybe return some JSON. Or render a template file.

Setting up autoloading

Composer automatically knows how to pull in external dependencies and make them available to our application. It does however not know what classes it should load from our own code. We need to configure that. To change that, we insert the following into our composer.json

"autoload": {
    "files": [ "src/helpers.php" ],
    "psr-4": {
        "Pretzel\\": "src/Pretzel"
    }
}

The first key, files, tells Composer specific files to load. This is useful if you have files that don't contain classes. And our helpers.php is just a collection of plain old functions.

The psr-4 key tells Composer which namespace to look for and where to look for it. Because I am always on-brand, I called my namespace Pretzel. Feel free to change this. We also tell Composer to look in the src/Pretzel directory for any classes it can find.

And since I always have trouble remembering this: It's namespace first, then the directory. The namespace has to end in an escaped backslash.

After you've made the changes, don't forget to run composer dump-autoload so the autoloader is aware of the new configuration.

Setting up our application with bootstrap.php

Whenever our application runs, there are certain things we want to do. We always want to load our .env file. We always want to load the Composer dependencies. If we have one, we always want to set up our library for database connections.

This all happens in bootstrap.php. For our basic project, all we need is 3 lines.

// bootstrap.php

<?php

require __DIR__ . '/vendor/autoload.php';

$dotenv = Dotenv\Dotenv::createImmutable([__DIR__]);
$dotenv->load();

The first require is a common sight for anyone who has dealt with Composer before. It loads all dependencies into our application.

Line 2 and 3 tell the vlucas/phpdotenv package to look in the current directory for a .env file and load it. If we don't have an .env file our application will yell at us about that and stop execution.

Adding the first helper

Now that we have set up the loading of our .env file, we have all the values contained in that file loaded into the $_ENV superglobal.

That's neat, but interacting with superglobals directly is generally considered a Bad Idea™. So instead, let's wrap our $_ENV variable into a nice env() function.

// src/helpers.php

<?php

if (!function_exists('env')) {
    function env($value, $default = null) {
        return $_ENV[$value] ?? $default;
    }
}

We have added the function_exists check, because some packages (e.g. illuminate/support) might bring along their own version of an env() function. If that's the case we don't want to overwrite it and just use the one they give us instead.

Inside the function we check if $_ENV holds the value we want. If it does not we use the null-coalescing operator ?? to get a default value. If there's no default value, we just return an empty value.

Making our application shareable

Often times when building projects, it can be useful to have a public endpoint that points to your local development environment. For example, when you're working with a third-party service that uses webhooks to notify you of events.

Thus it makes sense to make the application easily shareable.

For this, we need one more package. It's called expose and it's made by the cool people at BeyondCode. You can learn more about it on their website. Let's install it!

$ composer global require beyondcode/expose

Note that I'm installing it globally. You don't have to. It's just convenient.

But to share an application we need to have it be served by some kind of HTTP server. Many tutorials would go on a long tangent about setting up nginx or Apache here. For our little framework here, however, the built-in PHP server is plenty.

You call it like this:

$ php -S localhost:8080 -t public

Here the port can be anything you want. The -t option allows us to specify which directory is the public root of our application. In our case that's public/.

After running this you can open up your browser and look at localhost:8080 to see the result. Since we only have an empty directory at this point, you'll get a 404 error.

Now let's combine expose and the PHP server into one thing:

php -S localhost:8080 -t public &>/dev/null & expose share localhost:8080

There are some UNIX-y things happening here. Windows users may want to use WSL.

This command first starts up the server and redirects all output to /dev/null. That means we're essentially throwing it away. The PHP server just logs whatever requests are coming in anyways. Expose does the same thing, so we don't need that output.

The & after the first command tells the PHP server to continue running in the background. This allows us to immediately run another command, which is expose!

We point it to serve whatever is happening at localhost:8080, which is where our application currently lives!

And since all that is a bit of a hassle to type, we can wrap it in a so-called Composer script. To do so, add the following to your composer.json

"scripts": {
    "dev": "php -S localhost:8080 -t public &>/dev/null & expose share localhost:8080"
}

You can rename dev to whatever makes the most sense to you. And now that we've added this, we can run

$ composer run dev

and immediately have a publically shareable link to our app. Neat!

Using the framework

Now that everything is set up, we can use this for a very contrived example of building an application. It'll just take a value from our .env file and output it with some extra text we generate in a class.

To get started, let's add a value to our .env

MY_BIGGEST_SECRET="I love puppies and kittens"

Shocking, I know.

Next, let's create a PretzelFactory.php file in our src/ directory and add the following.

// src/PretzelFactory.php

class PretzelFactory
{
    public static function getPretzel()
    {
        return '🥨';
    }
}

This is not a factory in the true sense of the word, but it gives us an infinite supply of pretzels and that counts for something!

To wrap it up we create an index.php file in our public directory.

// public/index.php

// Our setup file
require __DIR__ . '/../bootstrap.php'; 

use Pretzel\PretzelFactory;

echo sprintf(
    '%s %s'
    PretzelFactory::getPretzel(),
    env('MY_BIGGEST_SECRET')
);

Here we read load in our bootstrap.php to get the app going. Then we load in the PretzelFactory class. Finally we output a formatted string containing a pretzel and our big secret.

And now we can share it like this

$ composer run dev

The result should look a little bit like this

A web page showing the text: 🥨 I love puppies and kittens

Closing thoughts

This concludes the basic project I will be using for my future blog posts. Anyone who wants to have a pre-made copy of it can clone it from GitHub.

In the future when starting from this point I will be linking this blog post and the repository, instead of explaining the entire process every time.

Unless of course we diverge from it. Then there will be a proper explanation.

Go to homepage