Codementor Events

Advanced SlimPHP Tutorial

Published Oct 19, 2017

swag-sticker-text.png

Well since my previous post on Slim seems to be well received here is a more advanced post. In this post I will be describing how I use Slim in the workplace and what I have found works really well.

Container

Pimple is a great container however I have found the lack of auto-wiring to be a bit of a pain so I do prefer to use PHP-DI via (http://php-di.org/doc/frameworks/slim.html). I use PHP-DI with a custom application class that looks like this:

<?php
namespace MyApp;

use DI\ContainerBuilder;

class App extends \DI\Bridge\Slim\App
{
    protected function configureContainer(ContainerBuilder $builder)
    {
        $dotenv = new \Dotenv\Dotenv(__DIR__.'/../config');
        $dotenv->load();

        $builder->addDefinitions(__DIR__ . '/../config/di-container.php');
    }
}

The di-container.php file is really simple and contains a PHP array that holds all of the "root" dependencies for a project. Typically this is limited to: Doctrine, Twig, Mailgun, Command Bus, Event Dipatcher, Redis ... etc. Having all of the "root" dependencies it saves me having to write all of the wiring code for my domain (which can be huge... Current project has 250+ Domain classes). If you have different configs that you need you can add infrastucture in your APP class to load different di-configs depending upon your environment.

This comes at a cost; performance... so just note that it's not free on average maybe 5ms will be added to your request.

A minimal PHP-DI di-container.php:

return [
  'settings.displayErrorDetails' => true, //This should be turned on for Dev and false for production!
];

Container Resolution

In Slim we support the Anonymous function style for route actions, but I suggest that no-one use them in production. Instead I have always recommended people use Action classes. Paul Jones wrote a nice article on how to use Action classes in Slim 3 Action-Domain-Responder.

Having said that as people start to use Slim and understand how PSR-4 works, I recommend that everyone use the class name for resolution of services inside the container. This is pretty much required when using PHP-DI however it's not really written anywhere. The class constant is very powerful especially when it comes to maintaining applications as if you move or refactor class names then PHPStorm is able to do all the things for you and you don't waste time changing strings!

$app->get('/user/{userId}', GetUserAction::class);

Remember Action classes must implement public function __invoke(Request $request, Response $response) and if you are using PHP-DI then the signature changes to include the userId param! public function __invoke($userId, Request $request, Response $response) I find this very very handy!

Route Groups

Route groups are a convienence feature which ends up being a URI builder. It is a very easy way to map middleware to entire blocks of routes and saves having to type out ->add(Middleware) everywhere.

$app->group('/api', function () {
  //The context inside here $this is actually the RouterInstance
  
    //User REST
    $this->group('/user', function () {
      $this->get('/{userId}', GetUserAction::class);
      $this->post('', CreateUserAction::class);
      $this->put('/{userId}', UpdateUserAction::class);
      $this->delete('/{userId}', DeleteUserAction::class);
    })->add(AclUserApiCheck::class);
    
    //Order REST
    $this->group('/order', function () {
      $this->get('/{order}', GetOrderAction::class);
      $this->post('', CreateOrderAction::class);
      $this->put('/{order}', UpdateOrderAction::class);
      $this->delete('/{order}', DeleteOrderAction::class);
    })->add(AclOrderApiCheck::class);
})->add(AclApiCheck::class);

I think this style ends up being more readable and is easier to trace when your routes file has a lot of route definitions.

Folder Structure

I personally use the following structure however there is absolutely no required structure for your application:

  • config/
  • templates/
  • src/
  • tests/
  • intl/
  • cil/
  • public/
  • logs/

Unit Testing Controllers/Actions

I have found that it's easier if you pass in an actual Slim object rather than a mocked one. The following code is a great place to start!

public function requestFactory()
    {
        $env = Environment::mock();
        $uri = Uri::createFromString('https://example.com:443/foo/bar?abc=123');
        $headers = Headers::createFromEnvironment($env);
        $cookies = [
            'user' => 'john',
            'id' => '123',
        ];
        $serverParams = $env->all();
        $body = new RequestBody();
        $uploadedFiles = UploadedFile::createFromEnvironment($env);
        $request = new Request('GET', $uri, $headers, $cookies, $serverParams, $body, $uploadedFiles);
        return $request;
    }

If you have any questions please feel free to reach out to me! I can be found on this platform, Twitter or on SlimPHP's (Slack Channel / IRC Channel)

Happy Coding!

Discover and read more posts from Glenn Eggleton
get started
post commentsBe the first to share your opinion
Daniel Opitz 🐘
7 years ago

Great article!

Is the folder structure inspired by the Standard PHP package skeleton (PDS)?

Why do you put the templates into the root directory (templates/) and not into resources/templates/ folder? I think Symfony 4 puts all the template files into the template/ folder too.

I would put the intl/ folder into resources/locale/.

What is cil/ ?

Where do you put all the temporary files?
Symfony 4 has a special folder for that: var/temp, var/logs/ and var/cache/.

Is there an article about the Slim Framework folder structure?

Glenn Eggleton
7 years ago

Hey Daniel. This is just my personal structure and one that we use at my place of work. I haven’t really followed the PDS so I wouldn’t want to comment on why they do things the way that they do. I have always preferred putting things in the root directory of a project. My applications are mounted at public/index.php so it’s fairly trivial to do directory magic to get to where I need to go. I really don’t think there’s a “Best” folder structure.

cli/ is for Command Line Interfaces… I like to write CLI apps that seed the database, run migrations… etc.

I don’t specifically place tmp files anywhere; they go in the default location which is /tmp/ for Unix based systems… I left out cache of my article I will add that in :) It might make sense to have another folder to group cache and and logs something to think about.

Slim doesn’t have a folder structure. It doesn’t matter how you structure your project! We do provide a starting point with our skeleton but you are free to change.

Paul M. Jones
7 years ago

I haven’t really followed the PDS so I wouldn’t want to comment on why they do things the way that they do.

FWIW, the pds/skeleton standard is based on research into 100k+ Composer projects, and codifies the commonalities among them. The publication itself is here and the research behind it is documented here.

Glenn Eggleton
7 years ago

Thanks Paul :) I’ll take a look soon.

Show more replies