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.
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.
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.
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.
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.
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.
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!
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
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.