Slim-OO Part 1 – Getting Started With Slim 2

Part 1. Getting started.

Introduction

So recently, when I needed to create a simple application skeleton out of the Slim v2 framework, I had a senior moment. As sometimes happens I couldn’t remember how I had set up a few things. I had to go back and look at notes and sources from previous applications I had built with Slim. Hey, I admit it, I’m getting old and there is a lot of stuff in this bald head to keep track of. So let’s document this build so we don’t have to dig through notebooks and git repositories.

There will be a link to the Github repository with the complete source at the end of each article.

Disclaimer

I have done all my development on Mac and Linux boxes for the last 15 years so I use the command line a lot and have PHP already installed by default. If you use Windows for development then I leave it to you to translate these commands and handle running PHP. I have no need or desire to figure that out. And honestly, if I had to use Windows I’d probably install Ubuntu on a Virtual Machine or use Docker or something.

Installing the framework and initial test.

This article is not about DevOps so I’m not going to go into any environment setup.
I’m going to assume at this point that you have an environment to develop in that is running some version
of PHP >= 5.5 and are using Composer
to manage your project dependencies. If not then I suggest checking out PHP The Right Way for an overview of modern PHP development techniques. It has a great section at the beginning about setting up development environments on both Mac and Windows.

Now, let’s create our project using the slim skeleton, then update it to the latest versions of everything.
I have Composer installed globally so I can just call $ composer from anywhere.
If you do not, and you install things on a per-project basis, you will need to call the phar file $ php composer.phar

Open a terminal, change to the directory where you keep your source code, and install the skeleton framework with these commands.

$ composer create-project slim/slim-skeleton slim-oo
$ cd slim-oo
$ composer update

This set of commands creates the project in the slim-oo directory, moves into that directory, and will update everything to the latest version by doing a composer update.

Okay, you should now have a working website. To view it, type this into your terminal to fire up PHP’s built-in web server.

$ php -S 0.0.0.0:8080 -t public public/index.php

Now open http://0.0.0.0:8080 in your browser. You should see the default Slim welcome page.

Initial modifications.

Slim-Skeleton comes with a very simplistic directory structure. This is fine if you only have a couple of pages but will become unruly as an application grows. I would like to rearrange some things to make them easier to maintain moving forward. Remember, the more organized you are at the beginning, the easier your life will be later on.

Let’s use the command line to move some things around and set up a more maintainable directory structure. From the project root run each of the following commands.

mkdir configuration
mkdir configuration/environments
touch configuration/environments/environment.env.example
mkdir -p configuration/middleware
mkdir -p configuration/routes
mkdir -p configuration/services
mv src/settings.php configuration/settings.php
mv src/middleware.php configuration/middleware/middleware.php
mv src/routes.php configuration/routes/routes.php
mv src/dependencies.php configuration/services/services.php
mkdir src/App
touch src/bootstrap.php
mkdir migrations
touch migrations/001.sql

You should now have a structure like this. The files that you touched are empty placeholders for now. We will fill those later when we do some refactoring.

├──project
|    └─configuration
|    |   └─environments
|    |   |   -environment.env.example
|    |   └─middleware
|    |   |   -middleware.php
|    |   └─routes
|    |   |   -routes.php
|    |   └─services
|    |   |   -services.php
|    |   -settings.php
|    └─logs
|    └─migrations
|    |   -001.sql
|    └─public
|    └─src
|    |   └─App
|    |       -Application.php
|    |   bootstrap.php
|    └─templates
|    └─vendors
|    .gitignore

Now let’s fix the index.php file so it works again. Change your requires in index.php to reflect the new paths, it should look like this.

    if (PHP_SAPI == 'cli-server') {
        // To help the built-in PHP dev server, check if the request was actually for
        // something which should probably be served as a static file
        $file = __DIR__ . $_SERVER['REQUEST_URI'];
        if (is_file($file)) {
            return false;
        }
    }

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

    session_start();

    // Instantiate the app
    $settings = require __DIR__ . '/../configuration/settings.php';
    $app = new \Slim\App($settings);

    // Set up dependencies
    require __DIR__ . '/../configuration/services/services.php';

    // Register middleware
    require __DIR__ . '/../configuration/middleware/middleware.php';

    // Register routes
    require __DIR__ . '/../configuration/routes/routes.php';

    // Run app
    $app->run();

The Slim welcome page should now show again in your browser.

Refactor time.

Now let’s make this project feel more object-oriented and move all of the setups into an application object.

First, let’s wrap our autoloader so we can add manual configurations to the generated autoloader that Composer gives us.
Open the recently created src/bootstrap.php and add the following.

// Set the project's base
if (!defined('APP_ROOT')) {
    $spl = new SplFileInfo(__DIR__ . '/..');
    define('APP_ROOT', $spl->getRealPath());
}

$loader = require_once APP_ROOT.'/vendor/autoload.php';

$settings = require APP_ROOT.'/configuration/settings.php';

return new App\Application($settings);

The first thing we do is create an APP_ROOT constant that we can access anywhere to find the root of our application.
We then pull in the autoloader that Composer has generated for us. This will let us pull in all of the classes in the
vendor directory using PHP’s use statement. No longer will we need to use require to find a class, just call the
Fully Qualified Namespace. This is what we do in the last line, returning an instance of our application.

Now remove the lines in your index.php that assign $settings and $app and replace them with the $app returned to
us from bootstrap.php. We can also remove the require for vendor/autoloader.php

session_start();

// Instantiate the app
$app = require __DIR__ . '/../src/bootstrap.php';

// Set up dependencies
require __DIR__ . '/../configuration/services/services.php';

Next, create a file called Application.php under the src/App directory. This will be our application object.
It will extend the Slim App object and allow us to do our own configurations and modifications as needed. Inject
the configuration file into the constructor as a parameter and pass it to the Slim base App’s constructor.

I have also added a getRootDir() method to the application object in case we need to get the contents of the
APP_ROOT constant in an object-oriented way.

namespace App;

use Slim\App;

/**
 * {@inheritDoc}
 */
class Application extends App
{
    private $dir = null;

    public function __construct($container = [])
    {
        parent::__construct($container);
    }

    /**
     * Get root directory.
     * @return string
     * @throws ConstantNotSetException
     */
    public function getRootDir()
    {
        if(!defined('APP_ROOT')){
            throw new ConstantNotSetException('Application root not defined.');
        }

        return APP_ROOT;
    }
}

Now we need to add our services, routes, and middleware back in. We will do this by creating a service loader.
This will automatically get all the definition files in our service, route, and middleware directories and install
them into the application’s Container. In this case Slim’s default, the
Pimple Container.

By using a Container we will be able to call classes without having to know how to set them up at run-time.
The Container will do that work for us. This is often referred to as Dependency Injection or Service Loading,
depending on your view. I don’t worry about semantics like that, all I care about is the end result.
I get my objects instantiated for me at run time and can develop applications more rapidly.

First, let’s add the configuration path to the settings file right after displayErrorDetails.

'settings' => [
    'displayErrorDetails' => true, // set to false in production
    'service_config_dir' => APP_ROOT.'/configuration/services/',
],

Now make the directory src/App/Loader and add a new file in it called ServiceLoader.php. This will contain a
class called ServiceLoader that we will use to pull in services’ closure configurations into our Container.

Here are the contents of that file

namespace App\Loader;

use App\Application;

class ServiceLoader
{
    /**
     * ServiceLoader constructor.
     */
    public function __construct(){}

    /**
     * Load all service definitions in the config services directory
     *
     * @param Application $app
     * @return Application
     */
    public function loadServices(Application $app)
    {
        $directory = $app->getContainer()["settings"]["service_config_dir"];
        foreach (array_diff(scandir($directory), ['..', '.']) as $filename) {
            $path = $directory . '/' . $filename;
            if (!is_dir($path)){
                require $path;
            }
        }
        return $app;
    }
}

The loadServices() method gets the service configuration directory from the container and pulls in every file in
that directory. Adding each file’s service definitions to our Container.

Don’t worry about the definitions for the moment, just load the default ones that come with Slim. We will go over
them and add some of our own in a future session.

Now, let’s go back to Application.php and create a method to call this class like so.

    private function loadServices()
    {
        $serviceLoader = new ServiceLoader();
        $serviceLoader->loadServices($this);
    }

Add a use statement to the top of the file to pull the ServiceLoader class.

    use App\Loader\ServiceLoader;

And finally, call it in the Application constructor after you call the parent constructor.

    parent::__construct($configuration);
    $this->loadServices();

Homework: Repeat for Middleware and Routes.

Now that you have your files coming in through the Application Loaders you can remove the require lines from the
index.php file.

Here is what the complete index.php looks like now. Very “Slim” if you ask me.

    if (PHP_SAPI == 'cli-server') {
        // To help the built-in PHP dev server, check if the request was actually for
        // something which should probably be served as a static file
        $file = __DIR__ . $_SERVER['REQUEST_URI'];
        if (is_file($file)) {
            return false;
        }
    }

    session_start();

    // Instantiate the app
    $app = require_once __DIR__ . '/../src/bootstrap.php';

    // Run app
    $app->run();

Autoloading.

The final step is to make sure our autoloader can find our source files in the src directory.
I will show you two different ways to do this, one manually and one magically. The manual way
is the one that will be used moving forward. The magic method is just here to make you aware of
its existence as you will most likely encounter it if you look into other code, such as third-party packages.

The manual way.

To manually add a namespace to the autoloader let’s open the bootstrap.php file. You will see that
the require of composer’s autoloader returns an instance of the loader. We can use this to add our
own PSR-4 namespace using the object’s setPsr4() method.

    $loader = require_once APP_ROOT.'/vendor/autoload.php';
    $loader->setPsr4('App\\', APP_ROOT.'/src/App');

This method takes the namespace’s prefix, in this case App\\, and the path on which the files can be found.
Be mindful of the double backslash or you will get errors. Why do we need two of them? Because we are using a
quoted string and need to escape the backslashes for the namespace. When using strings to define a Fully
Qualified Namespace.
string escaping rules apply.

The magic way.

The magic way is to tell composer to add our src directory to the generated autoloader build with our composer.json file.
To do this open your composer.json and add an autoload option after the require section.

  "require",: {
        "php": ">=5.5.0",
        "slim/slim": "^3.1",
        "slim/php-view": "^2.0",
        "monolog/monolog": "^1.17"
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/App"
    }
  }

Then run the following command to have composer rebuild the autoload script.

    $ composer dump-autoload

Our namespace would now be added to the generated vendor/composer/autoload_psr4.php file.

Opinion.

While the magic composer option is perfectly fine and gives a slight speed increase in loading, I don’t like having to run a console command every time I add or change a namespace during development. I like the finer control of adding it at run time in the bootstrap file while things are in flux. Once development on a namespace is completed it can be moved to the composer.json file to optimize the speed of the autoload process.

I usually save optimization until the end of development as you can’t optimize what doesn’t work.

NOTE:
Development usually comes in Three phases.

  • Developing: Making it work.
  • Refactoring: Making it work correctly.
  • Optimization: Making it work faster.

Finished.

That’s it for now. You should now be able to load the welcome page again. Yes, that seemed like a lot of work to get Slim to load the same page again. These changes and the ones that will come in future posts will make the framework easier to manage as it grows.

In the next installment, we will remove some duplication and refine the loading of things into the Container.

Code.

See the final code for this article on github. 001 Getting Started.